到 2015 2015年 7 月

30 卷數 7

蔚藍的內幕-事件分析和視覺化,第 3 部分樞紐

Bruno Terkaly |到 2015 2015年 7 月

如果你一直在關注這一系列,最終你會發現以前的主要目標實現工作在這最後一部分 — — 源自樹莓派設備在該欄位中的資料視覺化。這是圍繞物聯網 (物聯網) 場景三部分系列的最後一部分。前兩篇文章,請查看 4 月問題 (msdn.microsoft.com/magazine/dn948106) 和 6 月刊 (msdn.microsoft.com/magazine/mt147243)。

本月的專欄中的高級別目標是很簡單的。我會建造一個 Node.js 應用程式充當一個 Web 網站,並提供資料到行動裝置,然後將顯示的降雨資料的橫條圖。我需要 Node.js Web 服務器,因為它是不可取的行動裝置來直接存取資料存儲區。

其他技術可以有執行此功能,例如ASP.NETWeb API 或微軟 Azure API 的應用程式。大多數架構師會建議你進行篩選、 排序和分析中介層伺服器上,而不行動裝置本身中的資料。這是資訊的因為行動裝置有較少的計算能力並且你不想要跨線向設備發送大量。總之,讓伺服器做繁重的工作,讓該設備顯示的視覺效果。

入門

要在本月的專欄中運行示例,您需要已完成前兩名的 4 月和 6 月的問題。如果你還沒有,你也許可以手動建立出您自己的 DocumentDB 資料存儲區。

假如你以前創建了一個 DocumentDB 資料存儲區,首先去 Azure 門戶並查看 DocumentDB 資料庫。還有一些好的模具在門戶,您可以查看您的資料和執行不同的查詢。一旦您驗證資料有和理解其基本的結構,你可以開始建立 Node.js Web 服務器。

開發人員通常喜歡驗證軟體的有效性,為他們建造出來。很多開發人員開始進行單元測試,例如,用摩卡或茉莉花工具。例如,在生成之前移動用戶端,它很有意義以確保按預期執行 Node.js Web 服務器。一種方法是使用 Web 代理工具,如提琴手。這使得它容易發出 Web 請求 (如 HTTP GET) 和查看在本機的 JSON 格式的回應。這可以很有用,因為在構建時移動用戶端,您可以確信到移動用戶端,不是 Web 服務相關的任何問題。

為簡單起見,使用 Windows Phone 設備作為用戶端,即使 iOS 和 Android 設備是更為普遍。也許最好的辦法將基於 Web 的技術。這會讓你消耗或從任何設備,因此不將自己局限于行動裝置資料視覺化。另一種方法是使用本機的跨平台產品,如 Xamarin。

有幾個來源的資訊,你可以使用打造出了基於 Web 的移動用戶端,不是最重要的是 W3C 標準機構。你可以閱讀更多關於他們在標準文檔中 bit.ly/1PQ6uOt。Apache 軟體基金會也有做一大堆工作在這個空間中。閱讀更多關於在 cordova.apache.org。我在這裡集中在 Windows Phone,因為代碼是簡單明瞭,易於調試和簡單的解釋。

在入口網站 DocumentDB

這下一節,將基於以前的工作,其中我創建了一個 DocumentDB 市和溫度資料的資料存儲區作好準備。若要查找您的 DocumentDB 資料庫 (TemperatureDB),只需按一下功能表項目,流覽,然後選擇 DocumentDB 帳戶。你可以還鎖定您的資料庫作為瓷磚的主頁面上通過按右鍵,使主要的 Azure 門戶頁儀表板。你可以找到好綜述使用門戶工具交互與 DocumentDB 資料存儲在 bit.ly/112L4X1

在新的門戶真的有用的功能之一是能夠查詢並查看 DocumentDB,允許您查看其本機的 JSON 格式中的城市和溫度的資料中的實際資料。這大大簡化了你的能力,能夠輕鬆地更改 Node.js Web 服務器,並打造出了任何必要的查詢。

打造出了 Node.js Web 服務器

第一件事你需要做時創建您的 Node.js Web 服務器連接到 DocumentDB 資料存儲。在 Azure 入口網站上,你會發現所需的連接字串。從入口網站到連接字串,選擇 TemperatureDB DocumentDB 存儲,然後按一下在所有的設置,其次是鑰匙。

從那裡,您會需要兩條資訊。第一是對 DocumentDB 資料存儲的 URI。第二個是資訊安全金鑰 (在門戶稱為多個主鍵),使您可以執行從 Node.js Web 服務器的安全訪問。你會看到連接和資料庫資訊在下面的代碼:

{
  "HOST"       : "https://temperaturedb.documents.azure.com:443/",
  "AUTH_KEY"   : "secret key from the portal",
  "DATABASE"   : "TemperatureDB",
  "COLLECTION" : "CityTempCollection"
}

Node.js Web 服務器將讀取配置資訊。您的設定檔中的主機欄位會不同,因為所有 Uri 都是全域唯一。授權金鑰也是獨特的。

在以前的文章,溫度資料庫被命名為 TemperatureDB。每個資料庫可以有多個集合,但在這種情況下還有一個叫做 CityTemperature 的集合。集合是無非的文檔的清單。在此資料模型中,一個單獨的文檔是一個城市與 12 個月的溫度資料。

當你潛入了 Node.js Web 服務器的代碼的詳細資訊,您可以利用廣泛的生態系統的載入項圖書館­ies Node.js。 對於這個專案,您將使用兩個庫 (稱為節點套裝軟體)。第一個包是為 DocumentDB 功能。要安裝 DocumentDB 套裝軟體的命令是:新公共管理安裝 documentdb。第二個方案是用於讀取設定檔:新公共管理安裝 nconf。這些包提供附加的功能在預設安裝的 Node.js 失蹤。 您可以將有關建設出一個 Node.js 應用程式更廣泛的教程的蔚藍文檔中查找 DocumentDB bit.ly/1E7j5Wg

有七個部分在 Node.js Web 服務器中,如中所示圖 1。節 1 覆蓋連接到一些已安裝的套裝軟體,所以你得在後面的代碼中訪問這些。節 1 也定義移動用戶端將連接到的預設埠。當部署到 Azure,埠號由 Azure,因此 process.env.port 構建。

圖 1 內置了 Node.js Web 服務器

// +-----------------------------+
// |        Section 1            |
// +-----------------------------+
var http = require('http');
var port = process.env.port || 1337;
var DocumentDBClient = require('documentdb').DocumentClient;
var nconf = require('nconf');
// +-----------------------------+
// |        Section 2            |
// +-----------------------------+
// Tell nconf which config file to use
nconf.env();
nconf.file({ file: 'config.json' });
// Read the configuration data
var host = nconf.get("HOST");
var authKey = nconf.get("AUTH_KEY");
var databaseId = nconf.get("DATABASE");
var collectionId = nconf.get("COLLECTION");
// +-----------------------------+
// |        Section 3            |
// +-----------------------------+
var client = new DocumentDBClient(host, { masterKey: authKey });
// +-----------------------------+
// |        Section 4            |
// +-----------------------------+
http.createServer(function (req, res) {
  // Before you can query for Items in the document store, you need to ensure you
  // have a database with a collection, then use the collection
  // to read the documents.
  readOrCreateDatabase(function (database) {
    readOrCreateCollection(database, function (collection) {
      // Perform a query to retrieve data and display
      listItems(collection, function (items) {
        var userString = JSON.stringify(items);
        var headers = {
          'Content-Type': 'application/json',
          'Content-Length': userString.length
        };
        res.write(userString);
        res.end();
      });
    });
  });
}).listen(8124,'localhost');  // 8124 seemed to be the
                              // port number that worked
                              // from my development machine.
// +-----------------------------+
// |        Section 5            |
// +-----------------------------+
// If the database does not exist, then create it, or return the database object.
// Use queryDatabases to check if a database with this name already exists. If you
// can't find one, then go ahead and use createDatabase to create a new database
// with the supplied identifier (from the configuration file) on the endpoint
// specified (also from the configuration file).
var readOrCreateDatabase = function (callback) {
  client.queryDatabases('SELECT * FROM root r WHERE r.id="' + databaseId +
    '"').toArray(function (err, results) {
    console.log('readOrCreateDatabase');
    if (err) {
      // Some error occured, rethrow up
      throw (err);
    }
    if (!err && results.length === 0) {
      // No error occured, but there were no results returned,
      // indicating no database exists matching the query.           
      client.createDatabase({ id: databaseId }, function (err, createdDatabase) {
        console.log('client.createDatabase');
        callback(createdDatabase);
      });
    } else {
      // we found a database
      console.log('found a database');
      callback(results[0]);
    }
  });
};
// +-----------------------------+
// |        Section 6            |
// +-----------------------------+
// If the collection does not exist for the database provided, create it,
// or return the collection object. As with readOrCreateDatabase, this method
// first tried to find a collection with the supplied identifier. If one exists,
// it is returned and if one does not exist it is created for you.
var readOrCreateCollection = function (database, callback) {
  client.queryCollections(database._self, 'SELECT * FROM root r WHERE r.id="' +
    collectionId + '"').toArray(function (err, results) {
    console.log('readOrCreateCollection');
    if (err) {
      // Some error occured, rethrow up
      throw (err);
    }
    if (!err && results.length === 0) {
      // No error occured, but there were no results returned, indicating no
      // collection exists in the provided database matching the query.
      client.createCollection(database._self, { id: collectionId },
        function (err, createdCollection) {
        console.log('client.createCollection');
        callback(createdCollection);
      });
    } else {
      // Found a collection
      console.log('found a collection');
      callback(results[0]);
    }
  });
};
// +-----------------------------+
// |        Section 7            |
// +-----------------------------+
// Query the provided collection for all non-complete items.
// Use queryDocuments to look for all documents in the collection that are
// not yet complete, or where completed = false. It uses the DocumentDB query
// grammar, which is based on ANSI - SQL to demonstrate this familiar, yet
// powerful querying capability.
var listItems = function (collection, callback) {
  client.queryDocuments(collection._self, 'SELECT c.City,
    c.Temperatures FROM c where c.id="WACO- TX"').toArray(function (err, docs) {
    console.log('called listItems');
    if (err) {
      throw (err);
    }
    callback(docs);
  });
}

部分 2 讀取 config.json 檔,其中包含的連接資訊,包括資料庫和文件組合。它總是意義採取有關連接資訊的字串並將它們分別放在設定檔中。

部分 3 是你會使用與 DocumentDB 進行交互的用戶端連線物件。連接被傳遞到建構函式為 DocumentDBClient。

部分 4 表示執行一旦移動用戶端連接到 Node.js Web 服務器應用程式的代碼。CreateServer 是一個核心原始 Node.JS 申請,涉及概念圍繞事件迴圈和處理 HTTP 要求的數目。你可以閱讀更多關於那構造 (bit.ly/1FcNq1E)。

這表示高級別進入點連接到 Node.js Web 服務器的用戶端。它也是負責調用其他的 Node.js 代碼片段,從 DocumentDB 檢索的 JSON 資料。一旦檢索到資料,將它打包為一個基於 JSON 的有效載荷,並把它送到移動用戶端。它利用的請求和回應的物件,是 createServer 函數參數,(HTTP.createServer (功能 (必需,res)......)。

節 5 開始 DocumentDB 查詢處理。DocumentDB 資料存儲區可以包含多個資料庫。第五條目的是縮小資料在 DocumentDB URI 和指向特定資料庫。在這種情況下,這是 TemperatureDB。您還會看到一些額外的代碼,並不直接使用,但那裡是純粹為教育目的。您還會注意到一些代碼來創建一個資料庫,如果一個人並不存在。很多第 5 節和超越的邏輯基於 DocumentDB npm 包之前安裝。

節 6 表示在資料檢索過程中的下一步。這裡的代碼自動調用第 5 節中代碼的結果。 節 6 進一步縮小資料,深入利用建立的資料庫的文件組合 (溫度­DB) 第 5 節中。 您會注意到的 select 語句包含一個 where 子句為 CityTemperature 集合。這包括一些代碼來創建一個集合,如果不存在。

節 7 代表最後查詢執行之前到移動用戶端返回資料。為了保持事情簡單,查詢是硬編碼,以返回德克薩斯州韋科市的溫度資料。在現實的場景中,移動用戶端會通過在城市 (基於使用者輸入或可能的設備位置)。Node.js Web 服務器解析傳入的城市然後將其追加到哪裡第 7 節中的條款。

Node.js Web 服務器現已完成並準備執行。一次它的向上和運行它,它將無限期地等待用戶端請求從行動裝置。你會在你的開發機器上本地運行 Node.js Web 服務器。在這一點上,它使用有意義的提琴手開始測試 Node.js Web 服務器。提琴手允許您向 Web 服務發出 HTTP 要求 (在這種情況下,GET) 和查看的回應。驗證在提琴手的行為可以説明您解決任何問題之前構建移動用戶端。

現在你已經準備打造出了移動的用戶端,包括兩個基本的建築組成部分 — — XAML UI 和 CS 代碼-­(程式設計邏輯的生活) 的背後。在所示的代碼圖 2 表示標記移動用戶端上的視覺化介面。

圖 2 移動用戶端主畫面橫條圖的 XAML 標記

<Page
  x:Class="CityTempApp.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:CityTempApp"
  xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  xmlns:charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting"
  Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid>
    <!-- WinRTXamlToolkit chart control -->
    <charting:Chart
      x:Name="BarChart"
      Title=""
      Margin="5,0">
      <charting:BarSeries
        Title="Rain (in)"
        IndependentValueBinding="{Binding Name}"
        DependentValueBinding="{Binding Value}"
        IsSelectionEnabled="True"/>
      </charting:Chart>
  </Grid>
</Page>

請注意,它集成了 WinRTXamlToolkit,你可以找到在 CodePlex bit.ly/1PQdXwO。此工具組包括許多有趣的控制項。你會用的是圖表控制項。以圖表資料,只需建立一個名稱/值集合並將其附加到該控制項。在這種情況下,你會建造出的名稱/值集合的降雨資料為每個月在一個給定的城市。

在演示之前最後的解決辦法,有一些注意事項。都無法否認使用本機 Windows Phone 應用程式,支援更多基於 Web 的方法。

建在這裡的移動用戶端需要大量的快捷方式來的光禿的最低功能。例如,橫條圖將顯示您運行 Windows Phone 用戶端後,立即。這是因為有到 Node.js Web 服務器是一個 Web 請求,從 OnNavigatedTo 事件。這會自動執行一次,在 Windows Phone 用戶端啟動。你可以看到在部分 1 中所示的移動用戶端代碼圖 3

圖 3 為移動用戶端的的代碼

public sealed partial class MainPage : Page
{
  public MainPage()
  {
    this.InitializeComponent();
    this.NavigationCacheMode = NavigationCacheMode.Required;
  }
  // +-----------------------------+
  // |        Section 1            |
  // +-----------------------------+
  protected override void OnNavigatedTo(NavigationEventArgs e)
  {
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
      "http://localhost:8124");
    request.BeginGetResponse(MyCallBack, request);
  }
  // +-----------------------------+
  // |        Section 2            |
  // +-----------------------------+
  async void MyCallBack(IAsyncResult result)
  {
    HttpWebRequest request = result.AsyncState as HttpWebRequest;
    if (request != null)
    {
      try
      {
        WebResponse response = request.EndGetResponse(result);
        Stream stream = response.GetResponseStream();
        StreamReader reader = new StreamReader(stream);
        JsonSerializer serializer = new JsonSerializer();
        // +-----------------------------+
        // |        Section 3            |
        // +-----------------------------+
        // Data structures coming back from Node
        List<CityTemp> cityTemp = (List<CityTemp>)serializer.Deserialize(
          reader, typeof(List<CityTemp>));
        // Data structure suitable for the chart control on the phone
        List<NameValueItem> items = new List<NameValueItem>();
        string[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
        for (int i = 11; i >= 0; i-- )
        {
          items.Add(new NameValueItem { Name = months[i], Value =
            cityTemp[0].Temperatures[i] });
        }
        // +-----------------------------+
        // |        Section 4            |
        // +-----------------------------+
        // Provide bar chart the data
        await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
          this.BarChart.Title = cityTemp[0].City + ", 2014";
          ((BarSeries)this.BarChart.Series[0]).ItemsSource = items;
        });
      }
      catch (WebException e)
      {
        return;
      }
    }
  }
}
// +-----------------------------+
// |        Section 5            |
// +-----------------------------+
// Data structures coming back from Node
public class CityTemp
{
  public string City { get; set;  }
  public List<double> Temperatures { get; set; }
}
// Data structure suitable for the chart control on the phone
public class NameValueItem
{
  public string Name { get; set; }
  public double Value { get; set; }
}

也第 1 節,你會注意到它連接到本地主機上運行的 Node.js Web 服務器。很明顯,如果你主人在公共雲,例如 Azure 網站 Node.js Web 服務器指定不同的端點。後發出 Web 請求,您設置了使用 BeginGetResponse 的回檔。Web 請求是非同步所以代碼將設置回檔 (MyCallBack)。這導致部分 2,資料的檢索、 處理和載入到一個圖表控制項。

第 2 節中為非同步 Web 請求的回檔概述部分 1 過程中發回了 Web 服務的有效載荷。序列化的資料,使用 JSON 格式的 Web 回應程式碼。第三部分是關於 JSON 資料轉化第 5 節中定義的兩個單獨的資料結構。 目標是創建一個清單的名稱/值的陣列或清單。NameValueItem 類是圖表控制項需要的結構。

第四部分是有關使用 GUI 執行緒分配給圖表控制項的名稱/值清單。你可以看到專案源集合中的專案分配。等待這。Dispatcher.RunAsync 語法利用 GUI 執行緒更新可視控制項。如果您嘗試更新作為一個加工資料和製作 Web 請求使用相同的執行緒的可視介面,代碼不能正常工作。

現在你可以運行移動用戶端。你可能會丟失一些溫度資料,雖然,所以所有的欄控制項可能不會出現。

總結

就此結束三部分的系列,在這裡我設置為顯示一個端到端物聯網方案 — — 從資料攝取到將資料保持到資料視覺化。我開始這個系列通過攝取使用下 Ubuntu,作為努力類比下覆盆子 Pi 設備運行所需要的代碼運行的 C 程式的資料。您可以插入到 Azure 事件集線器從溫度感應器捕獲的資料。然而,是短暫的資料存儲在這裡,你需要將它移動到更永久存儲。

那把你帶到第二篇文章,一個後臺進程從事件集線器接收資料並將它移動到 Azure SQL 資料庫和 DocumentDB。然後這最後一部分暴露到行動裝置使用中介層運行 Node.js 這永久存儲的資料。

有很多潛在的擴展和改進,可以執行。例如,您可以探索的一個領域是機器學習和分析的概念。橫條圖視覺化回答的基本問題,"發生了什麼事?"更有趣的問題可能是:"會發生什麼?"換句話說,你能那麼預測未來降雨呢?最終的問題可能是,"什麼應該我做今天基於未來預測嗎?"


Bruno Terkaly 是在微軟,目的是發展的業界領先的應用程式和服務啟用跨設備的主要軟體工程師。他是負責開車穿越美國的頂尖雲計算和移動的機會和超越從技術支援的角度。他説明合作夥伴帶來市場及其應用提供建築指導和深厚的技術接合 ISV 的評價、 開發和部署過程。Terkaly 還與雲計算和移動工程組,提供回饋和影響路線圖密切合作。

感謝以下的微軟技術專家對本文的審閱:Leland Holmquest、David馬尼奧蒂和尼克 Trogh
David馬尼奧蒂 (微軟)Leland Holmquest(微軟),尼克 Trogh (微軟)

David馬尼奧蒂是與微軟,專業從事軟體發展的總理現場工程師。他主要支援 Microsoft 企業客戶通過建立概念證明,開展諮詢服務,以及提供講習班。他經常可以發現,調試損毀傾印和分析 windows 性能跟蹤。

Leland Holmquest是 Microsoft 服務、 MSDN 雜誌長期歌迷和支援者 do'ers 的知識管理平臺解決方案經理!

Nick Trogh 是在微軟,在那裡他可以説明開發人員在 Microsoft 平臺上實現軟體夢想技術福音傳教士。你可以找到他博客、 微博或會上開發人員活動。