2017 年 6 月

第 32 卷,第 6 期

物联网 - 使用 Azure IoT 套件推动 IoT 开发

作者 Dawid Borycki

物联网 (IoT) 解决方案包括远程遥测设备、Web 门户、云存储和实时处理功能。结构如此复杂,你就更不愿意开始进行 IoT 开发了。为了简化工作,Microsoft Azure IoT 套件提供了以下两个预配置解决方案:远程监视和预测性维护。本文将介绍如何创建远程监视解决方案,从而收集和分析 Windows 10 IoT 核心版控制的远程 IoT 设备中的数据。此 Raspberry Pi 设备将通过 USB 摄像头采集图像。随后,图像亮度会在 IoT 设备上进行计算,然后流式传输到云中,并在云中进行存储、处理和显示(见图 1)。此外,最终用户不仅可以查看通过远程设备采集的信息,还能远程控制相应设备。有关辅助此论述的完整源代码,请访问 msdn.com/magazine/0617magcode

Azure IoT 套件预配置解决方案门户,以及用于采集并处理视频流的远程通用 Windows 平台应用
图 1:Azure IoT 套件预配置解决方案门户,以及用于采集并处理视频流的远程通用 Windows 平台应用****

远程设备

Frank LaVigne (msdn.com/magazine/mt694090) 和 Bruno Sonnino (msdn.com/magazine/mt808503) 已在本杂志中介绍过在安装了 Windows 10 IoT 核心版的 Raspberry Pi 上进行编程的基础知识。LaVigne 和 Sonnino 介绍了如何设置开发环境和 IoT 开发板、如何在浏览器中使用设备门户配置 IoT 单元,以及如何使用 Windows 10 IoT 核心版控制 GPIO 端口。此外,LaVigne 还在他的文章中提到,可以使用 IoT 对远程摄像头进行编程和控制。在本文中,我扩展了这一想法,并明确介绍了如何将 Raspberry Pi 变成这样的设备。

为此,我使用空白应用(通用 Windows)Visual C# 项目模板创建了 RemoteCamera 通用 Windows 平台 (UWP) 应用,然后将目标和最低 API 版本设置为 Windows 10 周年版本(10.0;生成号 14393)。我使用此 API 版本是为了能够绑定到方法,从而直接将视图模型的方法与可视控件触发的事件相关联:

<Button x:Name="ButtonPreviewStart"
        Content="Start preview"
        Click="{x:Bind remoteCameraViewModel.PreviewStart}" />

接下来,我声明了 UI,如图 1 所示。有两个选项卡: “摄像头捕捉”和“云”。第一个选项卡中的控件可用于启动或停止摄像头预览、显示视频流并呈现图像亮度(标签和进度栏)。第二个选项卡中有两个按钮,分别用于将设备连入云中,以及在 IoT 门户中注册设备。“云”选项卡还包含一个复选框,用于启用流式传输遥测数据。

与 UI 相关联的大部分逻辑是在 RemoteCameraViewModel 类(见 RemoteCamera 项目的 ViewModels 子文件夹)中实现。除了实现一些与 UI 绑定的属性之外,此类还负责处理视频采集、图像处理和云交互。这些子功能分别是在以下各个类中实现: CameraCapture、ImageProcessor 和 CloudHelper。我很快将会介绍 CameraCapture 和 ImageProcessor,而 CloudHelper 及相关帮助程序类则稍后将在 Azure IoT 预配置解决方案上下文中进行介绍。

相机捕捉

摄像头捕捉(见 Helpers 文件夹下的 CameraCapture.cs)是在以下两个元素的基础之上生成: Windows.Media.Capture.MediaCapture class 和 Windows.UI.Xaml.Controls.CaptureElement。前者用于采集视频,而后者则用于显示采集的视频流。由于使用摄像头采集视频,因此必须在 Package.appxmanifest 中声明相应的设备功能。

若要初始化 MediaCapture 类,请调用 InitializeAsync 方法。最终,可以向此方法传递 MediaCaptureInitializationSettings 类的实例,从而指定捕捉选项。可以选择是流式传输视频还是音频,并选择捕捉硬件。在本文中,我仅通过默认摄像头采集视频(见图 2****)。

图 2:摄像头捕捉初始化

public MediaCapture { get; private set; } = new MediaCapture();
public bool IsInitialized { get; private set; } = false;
public async Task Initialize(CaptureElement captureElement)
{
  if (!IsInitialized)
  {
    var settings = new MediaCaptureInitializationSettings()
    {
      StreamingCaptureMode = StreamingCaptureMode.Video
    };
    try
    {
      await MediaCapture.InitializeAsync(settings);
      GetVideoProperties();
      captureElement.Source = MediaCapture;      IsInitialized = true;
    }
    catch (Exception ex)
    {
      Debug.WriteLine(ex.Message);
      IsInitialized = false;
    }
  }
}

接下来,使用 CaptureElement 类实例的 Source 属性,将此对象与 MediaCapture 控件相关联,以显示视频流。我还调用了帮助程序方法 GetVideoProperties,用于读取并存储视频帧的大小。稍后会使用此信息来获取预览帧以供处理。最后,为了能够真正启动和停止视频采集,我调用了 MediaCapture 类的 StartPreviewAsync 和 StopPreviewAsync。在 CameraCapture 中,我使用其他逻辑包装了这些方法,同时验证了初始化和预览状态:

public async Task Start()
{
  if (IsInitialized)
  {
    if (!IsPreviewActive)
    {
      await MediaCapture.StartPreviewAsync();
      IsPreviewActive = true;
    }
  }
}

运行应用时,可以按“开始预览”按钮来配置摄像头采集,之后不久就可以看到摄像头图像。请注意,由于 RemoteCamera 应用是通用应用,因此无需进行任何更改,即可部署到开发 PC、智能手机、平板电脑或 Raspberry Pi。如果使用 Windows 10 PC 测试 RemoteCamera 应用,需要确保应用可使用摄像头。可以使用“设置”应用(“隐私”/“摄像头”)来配置此设置。为了使用 Raspberry Pi 测试此应用,我使用了预算较低的 Microsoft Life Cam HD-3000。由于这是一个 USB 摄像头,因此,当我将它连到四个 Raspberry Pi USB 端口之一后,Windows 10 IoT 核心版可以自动检测到摄像头。有关与 Windows 10 IoT 核心版兼容的摄像头的完整列表,请访问 bit.ly/2p1ZHGD。将摄像头与 Rasbperry Pi 相连后,它显示在“设备门户”的“设备”选项卡下。

图像处理器

ImageProcessor 类在后台计算当前帧的亮度。为了执行后台操作,我使用基于任务的异步模式创建了线程,如图 3 所示。

图 3:在后台计算亮度

public event EventHandler<ImageProcessorEventArgs> ProcessingDone;
private void InitializeProcessingTask()
{
  processingCancellationTokenSource = new CancellationTokenSource();
  processingTask = new Task(async () =>
  {
    while (!processingCancellationTokenSource.IsCancellationRequested)
    {
      if (IsActive)
      {
        var brightness = await GetBrightness();
        ProcessingDone(this, new ImageProcessorEventArgs(brightness));
        Task.Delay(delay).Wait();
      }
    }
  }, processingCancellationTokenSource.Token);
}

在 while 循环中,我确定了图像亮度,然后将此值传递给 ProcessingDone 事件的侦听器。系统向此事件馈送 ImageProcessorEventArgs 类的实例,其中只有一个公共属性 Brightness。在收到取消信号前,处理任务会一直运行。图像处理的关键元素是 GetBrightness 方法,如图 4**** 所示。

图 4:GetBrightness 方法

private async Task<byte> GetBrightness()
{
  var brightness = new byte();
  if (cameraCapture.IsPreviewActive)
  {
    // Get current preview bitmap
    var previewBitmap = await cameraCapture.GetPreviewBitmap();
    // Get underlying pixel data
    var pixelBuffer = GetPixelBuffer(previewBitmap);
    // Process buffer to determine mean gray value (brightness)
    brightness = CalculateMeanGrayValue(pixelBuffer);
  }
  return brightness;
}

我使用 CameraCapture 类实例的 GetPreviewBitmap 来获取预览帧。在内部,GetPreviewBitmap 使用 MediaCapture 类的 GetPreviewFrameAsync。GetPreviewFrameAsync 有两个版本。第一个版本是无参数方法,返回的是 VideoFrame 类的实例。在这种情况下,可以通过读取 Direct3DSurface 属性来获取实际的像素数据。第二个版本接受 Video­Frame 类的实例,并将像素数据复制到其 SoftwareBitmap 属性中。在本文中,我使用第二个选项(见 CameraCapture 类的 GetPreviewBitmap 方法),然后通过 SoftwareBitmap 类实例的 CopyToBuffer 方法访问像素数据(如图 5 所示)。

图 5:访问像素数据

private byte[] GetPixelBuffer(SoftwareBitmap softwareBitmap)
{
  // Ensure bitmap pixel format is Bgra8
  if (softwareBitmap.BitmapPixelFormat != CameraCapture.BitmapPixelFormat)
  {
    SoftwareBitmap.Convert(softwareBitmap, CameraCapture.BitmapPixelFormat);
  }
  // Lock underlying bitmap buffer
  var bitmapBuffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read);
  // Use plane description to determine bitmap height
  // and stride (the actual buffer width)
  var planeDescription = bitmapBuffer.GetPlaneDescription(0);
  var pixelBuffer = new byte[planeDescription.Height * planeDescription.Stride];
  // Copy pixel data to a buffer
  softwareBitmap.CopyToBuffer(pixelBuffer.AsBuffer());
  return pixelBuffer;
}

首先,我将确认像素格式是否为 BGRA8。此像素格式表示图像使用四个 8 位通道:三个通道分别用于表示蓝色、绿色和红色,另外一个通道用于表示 alpha 或透明度。如果输入位图采用其他像素格式,我会执行相应的转换。接下来,我将把像素数据复制到字节数组中,其大小由图像高度与图像步幅的乘积决定 (bit.ly/2om8Ny9)。我从 BitmapPlaneDescription 实例中读取这两个值,此实例是通过 SoftwareBitmap.LockBuffer 方法返回的 BitmapBuffer 对象获取而来。

鉴于字节数组包含像素数据,只需计算所有像素的平均值即可。因此,我循环访问了像素缓存(见图 6****)。

图 6:计算像素的平均值

private byte CalculateMeanGrayValue(byte[] pixelBuffer)
{
  // Loop index increases by four since
  // there are four channels (blue, green, red and alpha).
  // Alpha is ignored for brightness calculation
  const int step = 4;
  double mean = 0.0;
  for (uint i = 0; i < pixelBuffer.Length; i += step)
  {
    mean += GetGrayscaleValue(pixelBuffer, i);
  }
  mean /= (pixelBuffer.Length / step);
  return Convert.ToByte(mean);
}

然后,每次循环访问时,我会计算所有颜色通道的平均值,将给定像素转换成灰度:

private static byte GetGrayscaleValue(byte[] pixelBuffer, uint startIndex)
{
  var grayValue = (pixelBuffer[startIndex]
    + pixelBuffer[startIndex + 1]
    + pixelBuffer[startIndex + 2]) / 3.0;
  return Convert.ToByte(grayValue);
}

Brightness 通过 ProcessingDone 事件传递给视图。此事件的处理位置为 MainPage 类 (MainPage.xaml.cs),我在其中通过标签和进度栏显示亮度。两个控件均绑定到 RemoteCameraViewModel 的 Brightness 属性。请注意,ProcessingDone 是由后台线程触发。因此,我使用 Dispatcher 类通过 UI 线程修改 RemoteCameraViewModel.Brightness,如图 7 所示。

图 7:使用 Dispatcher 类通过 UI 线程修改 RemoteCameraViewModel.Brightness

private async void DisplayBrightness(byte brightness)
{
  if (Dispatcher.HasThreadAccess)
  {
    remoteCameraViewModel.Brightness = brightness;
  }
  else
  {
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
      DisplayBrightness(brightness);
    });
  }
}

预配远程监视解决方案

若要预配解决方案,可以使用专用门户 (azureiotsuite.com)。登录并选择 Azure 订阅后,便会重定向到一个页面,可以在其中按“创建新的解决方案”矩形框。这会打开一个网站,可以在其中选择两个预配置解决方案之一:预测性维护或远程监视(见图 8****)。选择解决方案后,将会看到另一个窗体,可以在其中设置 Azure 资源的解决方案名称和区域。在本文中,我将解决方案名称和区域分别设置为“RemoteCameraMonitoring”和“美国西部”。

Azure IoT 套件预配置解决方案(上)和远程监视解决方案配置(下)
图 8:Azure IoT 套件预配置解决方案(上)和远程监视解决方案配置(下)

预配远程监视解决方案时,Azure IoT 套件门户会创建以下多个 Azure 资源: IoT 中心、流分析作业、存储和 App Service。IoT 中心可实现云和远程设备之间的双向通信。流分析作业会转换远程设备流式传输的数据,通常是筛选掉不必要的数据。筛选后的数据会进行存储或定向,以供将来分析时使用。最后,App Service 用于托管 Web 门户。

也可以通过命令行完成解决方案预配。为此,可以从 bit.ly/2osI4RW 克隆或下载解决方案源代码,然后按照 bit.ly/2p7MPPc 中的说明操作。也可以视需要在本地部署解决方案,如 bit.ly/2nEePNi 所述。在这种情况下,不会创建 Azure App Service,因为解决方案门户是在本地计算机上运行。若要进行开发和调试或修改预配置解决方案,就会发现此类方法特别有用。

完成预配后,可以启动解决方案,它的门户显示在默认浏览器中(再次见图 1****)。此门户包含多个选项卡。在本文中,我将仅关注其中两个选项卡,即“仪表板”和“设备”。“仪表板”显示远程设备及其流式传输的遥测数据的映射。“设备”选项卡显示远程设备列表,包括设备的状态、功能和说明。默认情况下,有多个仿真设备。我将介绍如何注册新的非仿真硬件。

注册设备

若要注册设备,请按解决方案门户中左下角的“添加设备”超链接。然后,选择添加仿真设备还是自定义设备。选取第二个选项,然后按“添加新设备”按钮。现在,可以定义“设备 ID”了。我将此值设置为“RemoteCamera”。此后,“添加自定义设备”窗体中显示设备凭据(见图 9),稍后用它将 IoT 设备连入 IoT 中心。

设备注册摘要
图 9:设备注册摘要****

设备元数据和云通信

添加的设备显示在设备列表中,然后便可以发送设备元数据或设备信息。设备信息包括描述远程设备的 JSON 对象。此对象可告知云终结点设备功能,并包含硬件描述以及设备接受的远程命令列表。最终用户可通过 IoT 解决方案门户向设备发送这些命令。在 RemoteCamera 应用中,设备信息表示为 DeviceInfo 类(位于 AzureHelpers 子文件夹中):

public class DeviceInfo
{
  public bool IsSimulatedDevice { get; set; }
  public string Version { get; set; }
  public string ObjectType { get; set; }
  public DeviceProperties DeviceProperties { get; set; }
  public Command[] Commands { get; set; }
}

DeviceInfo 的前两个属性指定了是否为仿真设备,并定义了 DeviceInfo 对象的版本。稍后可以看到,第三个属性 ObjectType 被设置为字符串常数 DeviceInfo。云(特别是 Azure 流分析作业)使用此字符串从遥测数据中筛选出设备信息。接下来,DeviceProperties(见 AzureHelpers 子文件夹)包含一系列描述设备的属性(如序列号、内存、平台、RAM)。最后,Commands 属性包含设备识别的一系列远程命令。通过指定名称和参数列表(分别由 Command 和 CommandParameter 类表示,见 AzureHelpers\Command.cs),可定义每个命令。

若要在 IoT 设备和 IoT 中心之间建立通信,请使用 Microsoft.Azure.Devices.Client NuGet 包。此包提供 DeviceClient 类,可用于向云发送消息和接收云消息。可以使用 Create 或 CreateFromConnectionString 静态方法,创建 DeviceClient 实例。在本文中,我使用第一个选项(见 AzureHelpers 文件夹中的 CloudHelper.cs):

public async Task Initialize()
{
  if (!IsInitialized)
  {
    deviceClient = DeviceClient.Create(
      Configuration.Hostname, Configuration.AuthenticationKey());
    await deviceClient.OpenAsync();
    IsInitialized = true;
    BeginRemoteCommandHandling();
  }
}

可以看到,若要使用 DeviceClient.Create 方法,需要提供 IoT 中心的主机名和设备凭据(标识符和密钥)。这些值是在设备预配期间从解决方案门户获取(再次见图 9)。在 RemoteCamera 应用中,我在 Configuration 静态类中存储了主机名、设备 ID 和密钥:

public static class Configuration
{
  public static string Hostname { get; } = "<iot-hub-name>.azure-devices.net";
  public static string DeviceId { get; } = "RemoteCamera";
  public static string DeviceKey { get; } = "<your_key>";
  public static DeviceAuthenticationWithRegistrySymmetricKey AuthenticationKey()
  {
    return new DeviceAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey);
  }
}

此外,Configuration 类还会实现静态方法 AuthenticationKey,从而将设备凭据包装到 DeviceAuthenticationWithRegistrySymmetricKey 类的实例中。我借此来简化 DeviceClient 类实例的创建工作。

连接建立后,只需发送 DeviceInfo 即可,如图 10**** 所示。

图 10:发送设备信息

public async Task SendDeviceInfo()
{
  var deviceInfo = new DeviceInfo()
  {
    IsSimulatedDevice = false,
    ObjectType = "DeviceInfo",
    Version = "1.0",
    DeviceProperties = new DeviceProperties(Configuration.DeviceId),
    // Commands collection
    Commands = new Command[]
    {
      CommandHelper.CreateCameraPreviewStatusCommand()
    }
  };
  await SendMessage(deviceInfo);
}

RemoteCamera 应用可发送描述实际硬件的设备信息,因此 IsSimulatedDevice 属性设置为 false。如上所述,ObjectType 设置为 DeviceInfo。此外,我还将 Version 属性设置为 1.0。对于 DeviceProperties,我使用的是任意值,主要包括静态字符串(见 DeviceProperties 类的 SetDefaultValues 方法)。我还定义了远程命令“更新摄像头预览”,以便能够远程控制摄像头预览。此命令包含一个布尔参数 IsPreviewActive,用于指定应启动还是停止摄像头预览(见 AzureHelpers 文件夹下的 CommandHelper.cs 文件)。

为了能够真正将数据发送到云中,我实现了 SendMessage 方法:

private async Task SendMessage(Object message)
{
  var serializedMessage = MessageHelper.Serialize(message);
  await deviceClient.SendEventAsync(serializedMessage);
}

一般来说,需要将 C# 对象序列化成包含 JSON 格式对象的字节数组(见 AzureHelpers 子文件夹中的 MessageHelper 静态类):

public static Message Serialize(object obj)
{
  ArgumentCheck.IsNull(obj, "obj");
  var jsonData = JsonConvert.SerializeObject(obj);
  return new Message(Encoding.UTF8.GetBytes(jsonData));
}

然后,将生成的数组包装到 Message 类中,以使用 DeviceClient 类实例的 SendEventAsync 方法将其发送到云中。Message 类是对象,为原始数据(传输的 JSON 对象)补充了其他属性。这些属性用于跟踪设备与 IoT 中心之间发送的消息。

在 RemoteCamera 应用中,与云建立连接和发送设备信息是通过“云”选项卡上的两个按钮触发的: “连接和初始化”和“发送设备信息”。第一个按钮的 click 事件处理程序绑定到 RemoteCameraViewModel 的 Connect 方法:

public async Task Connect()
{
  await CloudHelper.Initialize();
  IsConnected = true;
}

第二个按钮的 click 事件处理程序与 CloudHelper 类实例的 SendDeviceInfo 方法相关联。此方法前面介绍过。

连入云后,还可以开始发送遥测数据,与发送设备信息相似。也就是说,可以使用 SendMessage 方法,向其传递遥测对象。在本文中,此对象是 Telemetry­Data 类的实例,只有一个属性 Brightness。下面的完整示例展示了如何将遥测数据发送到云中,具体是在 CloudHelper 类的 SendBrightness 方法内实现:

public async void SendBrightness(byte brightness)
{
  if (IsInitialized)
  {
    // Construct TelemetryData
    var telemetryData = new TelemetryData()
    {
      Brightness = brightness
    };
    // Serialize TelemetryData and send it to the cloud
    await SendMessage(telemetryData);
  }
}

在获取 ImageProcessor 计算的亮度后,便会立即调用 SendBrightness。ProcessingDone 事件处理程序负责执行此操作:

private void ImageProcessor_ProcessingDone(object sender, ImageProcessorEventArgs e)
{
  // Update display through dispatcher
  DisplayBrightness(e.Brightness);
  // Send telemetry
  if (remoteCameraViewModel.IsTelemetryActive)
  {
    remoteCameraViewModel.CloudHelper.SendBrightness(e.Brightness);
  }
}

因此,如果现在运行 RemoteCamera 应用,然后开始预览并连接云,将会看到亮度值显示在相应图表中,如图 1 所示。请注意,尽管预配置解决方案的仿真设备旨在以遥测数据形式发送温度和湿度,但也可以发送其他值。在本文中,我将发送亮度,它会自动显示在相应图表中。

处理远程命令

CloudHelper 类还实现方法来处理从云中收到的远程命令。同样,与 ImageProcessor 一样,我是在后台处理命令(见 CloudHelper 类的 BeginRemoteCommandHandling)。我将再次使用基于任务的异步模式:

private void BeginRemoteCommandHandling()
{
  Task.Run(async () =>
  {
    while (true)
    {
      var message = await deviceClient.ReceiveAsync();
      if (message != null)
      {
        await HandleIncomingMessage(message);
      }
    }
  });
}

此方法负责创建任务来持续分析从云终结点收到的消息。若要接收远程消息,请调用 DeviceClient 类的 ReceiveAsync 方法。ReceiveAsync 返回 Message 类的实例,可用于获取包含 JSON 格式远程命令数据的原始字节数组。然后,将此数组反序列化成 RemoteCommand 对象(见 AzureHelpers 文件夹中的 RemoteCommand.cs),具体是在 MessageHelper 类(见 AzureHelpers 子文件夹)中进行实现:

public static RemoteCommand Deserialize(Message message)
{
  ArgumentCheck.IsNull(message, "message");
  var jsonData = Encoding.UTF8.GetString(message.GetBytes());
  return JsonConvert.DeserializeObject<RemoteCommand>(
    jsonData);
}

虽然 RemoteCommand 包含多个属性,但通常只使用以下两个:名称和参数(其中包含命令名称和命令参数)。在 RemoteCamera 应用中,我使用这些值来确定是否按预期接收到了命令(见图 11****)。如果收到,我会触发 UpdateCameraPreviewCommandReceived 事件,将相应信息传递给侦听器,然后我会使用 DeviceClient 类的 CompleteAsync 方法,通知云已收到命令。如果命令无法识别,我会使用 RejectAsync 方法拒绝接收。

图 11:远程消息反序列化和分析

private async Task HandleIncomingMessage(Message message)
{
  try
  {
    // Deserialize message to remote command
    var remoteCommand = MessageHelper.Deserialize(message);
    // Parse command
    ParseCommand(remoteCommand);
    // Send confirmation to the cloud
    await deviceClient.CompleteAsync(message);
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.Message);
    // Reject message, if it was not parsed correctly
    await deviceClient.RejectAsync(message);
  }
}
private void ParseCommand(RemoteCommand remoteCommand)
{
  // Verify remote command name
  if (string.Compare(remoteCommand.Name,
    CommandHelper.CameraPreviewCommandName) == 0)
  {
    // Raise an event, when the valid command was received
    UpdateCameraPreviewCommandReceived(this,
      new UpdateCameraPreviewCommandEventArgs(
      remoteCommand.Parameters.IsPreviewActive));
  }
}

UpdateCameraPreviewCommandReceived 事件是在 MainPage 类中进行处理。我会停止或启动本地摄像头预览,具体视我通过远程命令获取的参数值而定。此操作会再次分派给 UI 线程,如图 12 所示。

图 12:更新摄像头预览

private async void CloudHelper_UpdateCameraPreviewCommandReceived(
  object sender, UpdateCameraPreviewCommandEventArgs e)
{
  if (Dispatcher.HasThreadAccess)
  {
    if (e.IsPreviewActive)
    {
      await remoteCameraViewModel.PreviewStart();
    }
    else
    {
      await remoteCameraViewModel.PreviewStop();
    }
  }
  else
  {
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
      CloudHelper_UpdateCameraPreviewCommandReceived(sender, e);
    });
  }
}

从云发送命令

最后,我将介绍可用于远程控制 IoT 设备的解决方案门户。为此,请使用“设备”选项卡,需要在其中查找并单击相应设备(见图 13****)。

显示 RemoteCamera 详细信息的 IoT 门户“设备”选项卡
图 13:显示 RemoteCamera 详细信息的 IoT 门户“设备”选项卡

这会激活设备详细信息窗格,可以在其中单击“命令”超链接。然后,将看到另一个窗体,用于选择并发送远程命令。此窗体的具体布局取决于所选择的命令。在本文中,我只有一个参数命令,因此只有一个复选框。如果取消选中此复选框并发送命令,RemoteCamera 应用便会停止预览。发送的所有命令及其状态显示在命令历史记录中,如图 14**** 所示。

用于向 IoT 设备发送远程命令的窗体
图 14:用于向 IoT 设备发送远程命令的窗体

总结

我介绍了如何设置远程监视 Azure IoT 套件预配置解决方案。此解决方案可收集并显示与远程设备连接的摄像头采集的图像相关信息,并能远程控制 IoT 设备。随附的源代码可以在任意 UWP 设备上运行,因此无需真正将它部署到 Raspberry Pi。本文以及远程监视解决方案的联机文档和源代码将有助于快速启动综合 IoT 开发,以实现实用的远程监视。可以看到,借助 Windows 10 IoT 核心版,可实现的远不止让 LED 灯闪烁那么简单。


Dawid Borycki**** 是软件工程师、生物医学研究员、作家和会议演讲者。**他喜欢学习有关软件实验和原型设计的新技术。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Rachel Appel