到 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.NET Web 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 还与云计算和移动工程组,提供反馈和影响路线图密切合作。

感谢以下的微软技术专家对本文的审阅:利兰 Holmquest,David 马尼奥蒂和尼克 Trogh
David Magnotti (微软),利兰 Holmquest (微软),尼克 Trogh (微软)

David Magnotti 是与微软,专业从事软件开发的总理现场工程师。他主要支持 Microsoft 企业客户通过建立概念证明,开展咨询服务,以及提供讲习班。他经常可以发现,调试故障转储和分析 windows 性能跟踪。

利兰 Holmquest 是 Microsoft 服务、 MSDN 杂志长期歌迷和支持者 do'ers 的知识管理平台解决方案经理!

Nick Trogh 是在微软,在那里他可以帮助开发人员在 Microsoft 平台上实现软件梦想技术福音传教士。你可以找到他博客、 微博或会上开发人员活动。