65.9K
CodeProject 正在变化。 阅读更多。
Home

Let's IoT Hub: 教程 3

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (3投票s)

2018年7月8日

CPOL

9分钟阅读

viewsIcon

10963

创建一个应用程序来下载并存储上传到您的 Azure IoT Hub 的数据。

前言

本教程假定您具备一定的编程背景知识,但无需了解 Raspbian(或任何 Linux 发行版)、GPIO 微处理器(Pi、Arduino 等)或 Python 的经验。可能会引用之前的教程。

我主要使用的编程语言是 Java、C#.NET 和 VB.NET。您可能会注意到我的 Python 代码与 Python 的标准约定和格式之间存在一些细微差异。这不应影响您对代码的理解。

目标

准备您的 Raspberry Pi 3,以便上传更多数据,并设置一个 .NET 应用程序,将所有数据保存到 CSV 文件中。

示例用例

  • 户外天气模块
  • 多摄像头捕获系统(将数据存储为字节数组)
  • 屋顶太阳能电池板功率检测器

入门

在您的 Pi 上收集大量稍后可能需要的数据是很棒的,但如果无法及时有效地传输它们,又有什么意义呢?让我们加载您在第一、二个教程中用 Python 编写的模块。

首先,让我们做一些清理工作。我们有一个 `main` 方法,但实际上并没有用它来做什么,除了实例化我们的客户端。在 `main()` 调用之后,我们有一个 `While` 循环,似乎每次我们需要做不同的事情时它都会改变。是时候重组了!

接下来的代码,我将留给您决定如何实现,因此我不会提供您的代码应该是什么样的严格副本。但是,这是我建议遵循的结构

def sendButtonState():
      global ...
      # Collect state and send as key/value pair
def main():
      global ..., sendButtonState
      # Instantiation code...
      while True:
            time.sleep(15) # 15 second intervals
            sendButtonState()
...
# Begin code execution
...
main()
# END OF MODULE

通过创建一个新的方法来检查按钮状态,我们可以轻松地让我们的 `main()` 做任何我们想做的事情,只需在 `While` 循环中添加和删除方法调用即可。

发送更多数据

现在我们的代码看起来更整洁了,我们需要编写另一个方法。我们当前的键/值传输方法只接受一个键和一个值,如果我们 K须发送多个数据对,这就很浪费。考虑一下如果您有三个传感器收集不同类型的数据。与其将所有内容转换为一个长而特殊格式化的 `string`,以后需要解析,不如将其保存为字典效率更高

“Temperature:74.4248422;Humidity:32.661892;Pressure:101.3088224”
vs.
索引 0:Temperature = 74.4248422
索引 1:Humidity = 32.661892
索引 2:Pressure = 101.3088224

这不仅更具可读性,而且更易于访问,也不需要解析协议。您只需检索数据,将 `string` 转换为数字(在本例中为 `float` 或 `double`),然后就可以使用这些值了。

值得注意的是,在我们当前的实现中,也许将数据压缩成 CSV 可接受的格式,然后直接将接收到的 `string` 保存到文件会更容易。但是,这意味着我们不期望在应用程序内查看或操作这些数据,而是会将数据导入到 Excel 或数据库等其他程序中进行进一步处理。

如果您想在获取实时数据时显示它,使用 IoT Hub 客户端提供的键/值对选项会更简洁、更通用。所以,让我们添加一些代码来利用这一点。

同样,我们关注的是易用性而不是粗制滥造的实现——所以我们必须创建一个新类作为我们的消息构建器。

class DataBuilder:
      m = 0
      ref_CLIENT = 0
      def __init__(this, ref_C):
            this.ref_CLIENT = ref_C
            this.m = IoTHubMessage(“DataBuilder message”)
      def send(this):
            this.ref_CLIENT.send_event_async(
                  this.m, send_confirm_callback, “builder”)
      def add(this, key, value):
            this.m.properties().add(key, value)

注释

  • 不要复制粘贴代码!格式差异可能导致意外行为。
  • Python 类的构造函数是 `__init__`,两边各有两个下划线。
  • 所有类方法声明参数都必须以 'self' 或 'this' 引用开头。但是,当将参数传递给方法时,您会跳过这个额外的参数。
  • 所有类变量都必须通过上述 'self'/'this' 引用进行访问。
  • 在 `send(this)` 中,“send_confirm_callback” 是一个全局方法,因此不需要引用。

我们如何使用这个类?每次准备发送消息时,我们都会实例化一个,收集来自各种传感器的数据点,然后让它传输最终消息。Python 的垃圾回收将自动处理之后不需要的对象。

       def main():
            ...
            while True:
                  time.sleep(15)
                  # Instantiate a builder, passing our CLIENT as arg
                  msgBuilder = DataBuilder(CLIENT)
                  # Add keys and values
                  msgBuilder.add(“buttonState”, str(readSwitch()))
                  # All done? Upload
                  msgBuilder.send()

`readSwitch()` 实际上返回一个枚举,它可以等同于 `GPIO.LOW` 或 `GPIO.HIGH`;实际返回值分别为 `0` 或 `1`。由于 Python 没有隐式的 `int` -> `string` 转换,我们需要在这里强制进行一些转换。由于 IoT Hub Client 消息类的 `properties().add()` 函数只接受 `string` 作为值,因此在 `DataBuilder.add()` 中添加键/值对之前,始终强制进行 `string` 转换可能会很有用。

请注意,我们在这里删除了 `sendButtonState()` 方法调用,因为我们试图减少每个间隔发送的消息数量。请记住,我们确实有消息限制,因此每个间隔发送一条以上消息将导致我们超出该限制。但是,我们可以保留该方法声明,以防我们将来需要手动触发它。

从**教程 2** 运行您的控制台应用程序,并开始连接到您的 IoT Hub。等待任何额外数据下载(如果您的 Python 代码在控制台未运行时仍在运行)。

一旦准备就绪,请运行您的 Python 代码。您应该能够看到控制台应用程序中的更新,显示数据说“DataBuilder message”,带有一个键/值对,它应该显示“buttonState”和“0”或“1”。

如果您愿意,可以修改 `msgBuilder.add()` 调用以发送“LOW”/“HIGH”而不是“0”/“1”。

创建 CSV 协议

现在我们的 `DataBuilder` 类工作正常并且能够发送多条消息,让我们开始保存这些数据。停止您的 Python 模块和控制台应用程序。

本教程将引导您创建一个相对灵活的 CSV 文件创建器。它可以在无需每次创建新文件的情况下运行,将新数据附加到单个文件中。它还可以接受新的数据标签以及遗漏的标签。如果您认为这没有必要,网上有很多资源描述了生成格式化字符串并将其保存到文件中的基本方法。

为了保持标签的正确顺序,我们需要保留所有已知标签的列表,并使用此列表来编译我们新接收到的数据。让我们编辑我们在**教程 2**中的代码。

请记住,本教程中使用 VB.NET;C# 将类似。

注意:对于这部分,Java 代码结构将非常相似,如果您偏爱 Java。

让我们删除嵌套的 `For` 循环内的当前 `WriteLine` 语句,并调用 `SimpleEventProcessor` 中一个当前不存在的方法,称为“NewDatapoint”。

Function ProcessEventsAsync(...) As Task Implements ...
      For Each eventData In messages
            ...
            For Each piece in eventData.Properties
                  NewDatapoint(piece.Key, piece.Value)
            Next
      Next
      ...
End Function

我们的想法是允许 `SimpleEventProcessor` 检测缺失的标签并相应地调整 CSV 输出。`NewDatapoint()` 方法需要帮助完成此操作。但是,它需要几个变量

Private KnownLabels As New List(Of String)
Private InsertData As New List(Of String)(0)

Function ProcessEventsAsync(...) As Task Implements ...
      ...
End Function

首先,我们搜索标签是否先前已知(因此具有预定的索引)。然后,我们将新值添加到列表中,以便稍后编译。

Sub NewDatapoint(key As String, value As String)
      If KnownLabels.Contains(key) Then
            Dim index = KnownLabels.IndexOf(key)
            InsertData.Insert(index, value)
      Else
            KnownLabels.Add(key)
            InsertData.Add(value)
      End If
End Sub    

提示:'Sub' 等同于 C# 的 `void`;它不返回值。

您可能会注意到这里存在可能在 `InsertData.Insert()` 处引发异常的情况——不用担心,我们现在将处理它

Sub RefreshDataList()
      InsertData = New List(Of String)(KnownLabels.Count)
End Sub

这样,`InsertData` 始终能够包含至少已知标签的数量。如果出现其他标签,`NewDatapoint` 将能够处理它。但是,让我们确保在正确的时间调用此新方法

Function ProcessEventsAsync(...) As Task Implements ...
      For Each eventData In messages
            'Make sure the datalist is correctly sized and empty
            RefreshDataList()
            ...
            For Each piece in eventData.Properties
                  NewDatapoint(piece.Key, piece.Value)
            Next
      Next
      ...
End Function

最后,我们需要允许 `SimpleEventProcessor` 写入几个文件。

Sub WriteToDisk()
      Dim FileLocation = My.Computer.FileSystem.CurrentDirectory
      Dim CSVFile As String = FileLocation & "/IoTHubData.CSV"
      Dim LabelFile As String = FileLocation & "/IoTHubLabels.txt"

      'Generate new data line and label file
      Dim Data As String = ""
      Dim LabelFileText As String = ""

      For i = 0 To KnownLabels.Count - 1
            LabelFileText += KnownLabels(i) & vbCrLf
            If Not IsNothing(InsertData(i)) AndAlso
                  InsertData(i) <> "" Then
                  Data += InsertData(i)
            End If
            Data += ","
      Next

      Data.TrimEnd(",")
      Data += vbCrLf
      'Append new data to existing file
      IO.File.AppendAllText(CSVFile, Data)

      LabelFileText.TrimEnd(vbCrLf)
      'Overwrite the known labels file
      IO.File.WriteAllText(LabelFile, LabelFileText)
End Sub

注释

  • 您可以将 `FileLocation` 更改为您选择的任何目录,只要该目录 `string` 以斜杠“/”字符结尾。
  • VB.NET 的 for 循环评估方式为(`i = 0; i <= MAX; i++`),因此有必要将计数减去 `1` 以匹配索引。
  • `IsNothing(a)` 等同于 `== null`。
  • <>” 等同于 “!=
  • &” 等同于 `obj.ToString + obj.ToString`
  • vbCrLf” 是一个换行符,类似于“\n
  • 有许多方法可用于写入文件;如果您有其他更喜欢的方法,请随时替换上面写的方法。只需确保它们的行为符合您的意图;即,覆盖与追加。

这是一个很长的方法,但它生成一个 CSV 文件,其中每个发送的集合都编译成一行,在未接收到数据的 K处留有 `null` 空间,并且能够容忍事后添加其他传感器数据。然后可以将 CSV 文件导入 Excel 或其他应用程序进行进一步分析或绘图。如果需要,可以从标签文件的单独行中读取数据列。

作为最后一步,我们还允许应用程序加载先前已知的标签,以便在不同运行之间保持顺序相同。为此,让我们生成一个构造函数。

Public Sub New()
      Dim FileLocation = My.Computer.FileSystem.CurrentDirectory
      Dim LabelFile As String = FileLocation & “/IoTHubLabels.txt”
      Dim labels = IO.File.ReadAllLines(LabelFile)
      KnownLabels = New List(Of String)(labels)
End Sub

请注意,我们之前没有将 `FileLocation` 和 `LabelFile` 作为类变量,所以我们在这里需要重写它们。如果您愿意,可以随意将它们重组为类变量,以防止在您决定以后重命名或重新定位文件时出现问题。就目前而言,坚持我们现有的代码就可以了。

但等等!第一次运行时,您将没有要读取的文件,因此“KnownLabels = New List”一行将引发异常。没问题,让我们用一行来快速修复它,以在文件不存在时创建文件。

Public Sub New()
      Dim FileLocation = My.Computer.FileSystem.CurrentDirectory
      Dim LabelFile As String = FileLocation & “/IoTHubLabels.txt”
      If Not IO.File.Exists(LabelFile) Then
            Dim f = IO.File.Create(LabelFile)
            'Required call to close the filestream
            f.Dispose()
      End If
      Dim labels = IO.File.ReadAllLines(LabelFile)
      KnownLabels = New List(Of String)(labels)
      'Ignore empty key on initial creation
      If KnownLabels.Count = 1 AndAlso KnownLabels(0) = "" Then
            KnownLabels.Remove(0)
      End If
End Sub

最后,我们需要在所有事件处理完毕后调用 `WriteToDisk`。

Function ProcessEventsAsync(...) As Task Implements ...
      For Each eventData In messages
            'Make sure the datalist is correctly sized and empty
            RefreshDataList()
            ...
            For Each piece in eventData.Properties
                  NewDatapoint(piece.Key, piece.Value)
            Next
      Next
      WriteToDisk()
      ...
End Function

运行您的控制台应用程序和 Python 模块!让它获取几个数据点,然后使用 Notepad++ 等应用程序检查文件。N++ 允许您在不关闭和重新打开文件的情况下刷新文件内容,因此您可以看到新数据是如何写入文件的。只需点击窗口外,然后将焦点移回 N++;将出现一个提示重新加载文件的提示。

结论

恭喜!您已成功创建了一个 .NET 控制台应用程序,该应用程序可以下载和查看通过 IoT Hub 接收的实时数据,并创建了一个包含所有这些数据的 CSV 文件。CSV 协议也足够健壮,可以扩展变量数量并跳过无效值,因此您无需手动调整文件。

还有什么可以尝试的?

您可以开始寻找要安装的传感器,并找到它们各自的 Python 库或参考文档。用您新创建的应用程序测试大量数据,并找出重置保存文件的 K佳时机。根据运行时间确定文件大小。

本系列其他教程

教程 1教程 2

© . All rights reserved.