将设备连接到远程监视预配置解决方案(Windows)

场景概述

在此方案中,你将创建一个设备,用于将以下遥测数据发送到远程监视 预配置解决方案

  • 外部温度
  • 内部温度
  • 湿度

为简单起见,设备上的代码会生成示例值,但我们建议你通过将真实传感器连接到设备并发送实际遥测数据来扩展示例。

设备还能够响应从解决方案仪表板调用的方法和解决方案仪表板中设置的所需属性值。

若要完成本教程,需要一个有效的 Azure 帐户。 如果没有帐户,只需花费几分钟就能创建一个免费试用帐户。 有关详细信息,请参阅 Azure 免费试用

开始之前

在为设备编写任何代码之前,必须预配远程监视预配置解决方案并在该解决方案中预配新的自定义设备。

配置您的远程监控预配置解决方案

本教程中创建的设备将数据发送到 远程监视 预配置解决方案的实例。 如果尚未在 Azure 帐户中预配远程监视预配置解决方案,请使用以下步骤:

  1. https://www.azureiotsolutions.com/ 页面上,单击 + 以创建解决方案。
  2. 单击“选择”远程监视面板以创建解决方案。
  3. “创建远程监视解决方案 ”页上,输入所选 的解决方案名称 ,选择要部署到 的区域 ,然后选择要使用的 Azure 订阅。 然后单击“ 创建解决方案”。
  4. 等待预配过程完成。

警告

预配置的解决方案使用可计费的 Azure 服务。 使用完预配置解决方案后,请务必从您的订阅中删除,以避免产生任何不必要的费用。 可以通过访问 https://www.azureiotsolutions.com/ 页面,从订阅中完全删除预配置解决方案。

远程监视解决方案的预配过程完成后,单击“ 启动 ”以在浏览器中打开解决方案仪表板。

解决方案仪表板

在远程监控解决方案中配置设备

注释

如果已在解决方案中预配了设备,则可以跳过此步骤。 创建客户端应用程序时,需要知道设备凭据。

若要使设备连接到预配置解决方案,它必须使用有效的凭据将自身标识到 IoT 中心。 可以从解决方案仪表板检索设备凭据。 在本教程中,稍后您将在客户端应用程序中包含设备凭据。

若要将设备添加到远程监视解决方案,请在解决方案仪表板中完成以下步骤:

  1. 在仪表板的左下角,单击 添加设备

    添加设备

  2. 自定义设备 面板中,单击 添加新

    添加自定义设备

  3. 选择让我定义自己的设备ID。 输入设备 ID(如 mydevice),单击 “检查 ID ”以验证名称是否尚未使用,然后单击“ 创建 ”预配设备。

    添加设备 ID

  4. 记下设备凭据(设备 ID、IoT 中心主机名和设备密钥)。 客户端应用程序需要这些值才能连接到远程监视解决方案。 然后单击“完成”。

    查看设备凭据

  5. 在解决方案仪表板的设备列表中选择设备。 然后,在 设备详细信息 面板中,单击 启用设备。 设备当前状态为 运行。 远程监视解决方案现在可以从设备接收遥测数据,并在设备上调用方法。

在 Windows 上创建 C 示例解决方案

以下步骤演示如何创建与远程监视预配置解决方案通信的客户端应用程序。 此应用程序以 C 编写,并在 Windows 上生成并运行。

在 Visual Studio 2015 或 Visual Studio 2017 中创建初学者项目,并添加 IoT 中心设备客户端 NuGet 包:

  1. 在 Visual Studio 中,使用 Visual C++ Win32 控制台应用程序 模板创建 C 控制台应用程序。 将项目命名为 RMDevice

  2. Win32 应用程序向导中的“应用程序设置”页上,确保选中控制台应用程序,并取消选中预编译标头和安全开发生命周期(SDL)检查

  3. 解决方案资源管理器中,删除 stdafx.h、targetver.h 和 stdafx.cpp文件。

  4. 解决方案资源管理器中,将文件RMDevice.cpp重命名为 RMDevice.c。

  5. 解决方案资源管理器中,右键单击 RMDevice 项目,然后单击“ 管理 NuGet 包”。 单击“ 浏览”,然后搜索并安装以下 NuGet 包:

    • Microsoft.Azure.IoTHub.Serializer
    • Microsoft.Azure.IoTHub.IoTHubClient
    • Microsoft.Azure.IoTHub.MqttTransport
  6. 解决方案资源管理器中,右键单击 RMDevice 项目,然后单击“ 属性 ”以打开项目的 “属性页 ”对话框。 有关详细信息,请参阅设置 Visual C++ 项目属性

  7. 单击 链接器 文件夹,然后单击 “输入 属性”页。

  8. crypt32.lib 添加到 Additional Dependencies 属性。 单击“ 确定 ”,然后再次 单击“确定 ”以保存项目属性值。

将 Parson JSON 库添加到 RMDevice 项目,并添加所需的 #include 语句:

  1. 在计算机上的合适文件夹中,使用以下命令克隆 Parson GitHub 存储库:

    git clone https://github.com/kgabis/parson.git
    
  2. 将 Parson.h 和 parson.c 文件从 Parson 存储库的本地副本复制到 RMDevice 项目文件夹。

  3. 在 Visual Studio 中,右键单击 RMDevice 项目,单击“ 添加”,然后单击“ 现有项”。

  4. “添加现有项 ”对话框中,选择 RMDevice 项目文件夹中的 parson.h 和 parson.c 文件。 然后单击“ 添加” 将这两个文件添加到项目。

  5. 在 Visual Studio 中,打开 RMDevice.c 文件。 将现有 #include 语句替换为以下代码:

    #include "iothubtransportmqtt.h"
    #include "schemalib.h"
    #include "iothub_client.h"
    #include "serializer_devicetwin.h"
    #include "schemaserializer.h"
    #include "azure_c_shared_utility/threadapi.h"
    #include "azure_c_shared_utility/platform.h"
    #include "parson.h"
    

    注释

    现在,您可以通过生成项目来验证项目是否已正确设置依赖项。

指定 IoT 设备的行为

IoT 中心序列化程序客户端库使用模型来指定设备与 IoT 中心交换的消息的格式。

  1. 在语句后面 #include 添加以下变量声明。 将占位符值 [设备 ID] 和 [设备密钥] 替换为在远程监视解决方案仪表板中为设备记录的值。 使用解决方案仪表板中的 IoT 中心主机名替换 [IoTHub 名称]。 例如,如果 IoT 中心主机名 contoso.azure-devices.net,请将 [IoTHub Name] 替换为 contoso

    static const char* deviceId = "[Device Id]";
    static const char* connectionString = "HostName=[IoTHub Name].azure-devices.net;DeviceId=[Device Id];SharedAccessKey=[Device Key]";
    
  2. 添加以下代码以定义使设备能够与 IoT 中心通信的模型。 此模型规定该设备会:

    • 可以将温度、外部温度、湿度和设备 ID 作为遥测发送。
    • 可以将有关设备的元数据发送到 IoT 中心。 设备在启动时在 DeviceInfo 对象中发送基本元数据。
    • 可以将报告的属性发送到 IoT 中心的设备孪生。 这些报告的属性分组到配置、设备和系统属性中。
    • 可以接收和处理在 IoT 中心设备孪生中设置的所需属性。
    • 可以响应通过解决方案门户调用的 RebootInitiateFirmwareUpdate 直接方法。 设备发送有关它支持使用报告属性的直接方法的信息。
    // Define the Model
    BEGIN_NAMESPACE(Contoso);
    
    /* Reported properties */
    DECLARE_STRUCT(SystemProperties,
      ascii_char_ptr, Manufacturer,
      ascii_char_ptr, FirmwareVersion,
      ascii_char_ptr, InstalledRAM,
      ascii_char_ptr, ModelNumber,
      ascii_char_ptr, Platform,
      ascii_char_ptr, Processor,
      ascii_char_ptr, SerialNumber
    );
    
    DECLARE_STRUCT(LocationProperties,
      double, Latitude,
      double, Longitude
    );
    
    DECLARE_STRUCT(ReportedDeviceProperties,
      ascii_char_ptr, DeviceState,
      LocationProperties, Location
    );
    
    DECLARE_MODEL(ConfigProperties,
      WITH_REPORTED_PROPERTY(double, TemperatureMeanValue),
      WITH_REPORTED_PROPERTY(uint8_t, TelemetryInterval)
    );
    
    /* Part of DeviceInfo */
    DECLARE_STRUCT(DeviceProperties,
      ascii_char_ptr, DeviceID,
      _Bool, HubEnabledState
    );
    
    DECLARE_DEVICETWIN_MODEL(Thermostat,
      /* Telemetry (temperature, external temperature and humidity) */
      WITH_DATA(double, Temperature),
      WITH_DATA(double, ExternalTemperature),
      WITH_DATA(double, Humidity),
      WITH_DATA(ascii_char_ptr, DeviceId),
    
      /* DeviceInfo */
      WITH_DATA(ascii_char_ptr, ObjectType),
      WITH_DATA(_Bool, IsSimulatedDevice),
      WITH_DATA(ascii_char_ptr, Version),
      WITH_DATA(DeviceProperties, DeviceProperties),
    
      /* Device twin properties */
      WITH_REPORTED_PROPERTY(ReportedDeviceProperties, Device),
      WITH_REPORTED_PROPERTY(ConfigProperties, Config),
      WITH_REPORTED_PROPERTY(SystemProperties, System),
    
      WITH_DESIRED_PROPERTY(double, TemperatureMeanValue, onDesiredTemperatureMeanValue),
      WITH_DESIRED_PROPERTY(uint8_t, TelemetryInterval, onDesiredTelemetryInterval),
    
      /* Direct methods implemented by the device */
      WITH_METHOD(Reboot),
      WITH_METHOD(InitiateFirmwareUpdate, ascii_char_ptr, FwPackageURI),
    
      /* Register direct methods with solution portal */
      WITH_REPORTED_PROPERTY(ascii_char_ptr_no_quotes, SupportedMethods)
    );
    
    END_NAMESPACE(Contoso);
    

** 实现设备的功能行为

现在添加用于实现模型中定义的行为的代码。

  1. 添加以下用于处理解决方案仪表板中设置的所需属性的函数。 这些所需属性在模型中定义:

    void onDesiredTemperatureMeanValue(void* argument)
    {
      /* By convention 'argument' is of the type of the MODEL */
      Thermostat* thermostat = argument;
      printf("Received a new desired_TemperatureMeanValue = %f\r\n", thermostat->TemperatureMeanValue);
    
    }
    
    void onDesiredTelemetryInterval(void* argument)
    {
      /* By convention 'argument' is of the type of the MODEL */
      Thermostat* thermostat = argument;
      printf("Received a new desired_TelemetryInterval = %d\r\n", thermostat->TelemetryInterval);
    }
    
  2. 添加以下函数,用于处理通过 IoT 中心调用的直接方法。 这些直接方法在模型中定义:

    /* Handlers for direct methods */
    METHODRETURN_HANDLE Reboot(Thermostat* thermostat)
    {
      (void)(thermostat);
    
      METHODRETURN_HANDLE result = MethodReturn_Create(201, "\"Rebooting\"");
      printf("Received reboot request\r\n");
      return result;
    }
    
    METHODRETURN_HANDLE InitiateFirmwareUpdate(Thermostat* thermostat, ascii_char_ptr FwPackageURI)
    {
      (void)(thermostat);
    
      METHODRETURN_HANDLE result = MethodReturn_Create(201, "\"Initiating Firmware Update\"");
      printf("Recieved firmware update request. Use package at: %s\r\n", FwPackageURI);
      return result;
    }
    
  3. 添加以下函数,用于向预配置解决方案发送消息:

    /* Send data to IoT Hub */
    static void sendMessage(IOTHUB_CLIENT_HANDLE iotHubClientHandle, const unsigned char* buffer, size_t size)
    {
      IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, size);
      if (messageHandle == NULL)
      {
        printf("unable to create a new IoTHubMessage\r\n");
      }
      else
      {
        if (IoTHubClient_SendEventAsync(iotHubClientHandle, messageHandle, NULL, NULL) != IOTHUB_CLIENT_OK)
        {
          printf("failed to hand over the message to IoTHubClient");
        }
        else
        {
          printf("IoTHubClient accepted the message for delivery\r\n");
        }
    
        IoTHubMessage_Destroy(messageHandle);
      }
      free((void*)buffer);
    }
    
  4. 添加以下回调处理程序,该处理程序在设备向预配置解决方案发送新的报告属性值时运行:

    /* Callback after sending reported properties */
    void deviceTwinCallback(int status_code, void* userContextCallback)
    {
      (void)(userContextCallback);
      printf("IoTHub: reported properties delivered with status_code = %u\n", status_code);
    }
    
  5. 添加以下函数,将设备连接到云中的预配置解决方案,并交换数据。 此函数执行以下步骤:

    • 初始化平台。
    • 将 Contoso 命名空间注册到序列化库。
    • 使用设备连接字符串初始化客户端。
    • 创建 恒温器 模型的实例。
    • 创建并发送已报告的属性值。
    • 发送 DeviceInfo 对象。
    • 创建一个循环,每秒发送遥测数据。
    • 解除初始化所有资源。
    void remote_monitoring_run(void)
    {
      if (platform_init() != 0)
      {
        printf("Failed to initialize the platform.\n");
      }
      else
      {
        if (SERIALIZER_REGISTER_NAMESPACE(Contoso) == NULL)
        {
          printf("Unable to SERIALIZER_REGISTER_NAMESPACE\n");
        }
        else
        {
          IOTHUB_CLIENT_HANDLE iotHubClientHandle = IoTHubClient_CreateFromConnectionString(connectionString, MQTT_Protocol);
          if (iotHubClientHandle == NULL)
          {
            printf("Failure in IoTHubClient_CreateFromConnectionString\n");
          }
          else
          {
    #ifdef MBED_BUILD_TIMESTAMP
            // For mbed add the certificate information
            if (IoTHubClient_SetOption(iotHubClientHandle, "TrustedCerts", certificates) != IOTHUB_CLIENT_OK)
            {
                printf("Failed to set option \"TrustedCerts\"\n");
            }
    #endif // MBED_BUILD_TIMESTAMP
            Thermostat* thermostat = IoTHubDeviceTwin_CreateThermostat(iotHubClientHandle);
            if (thermostat == NULL)
            {
              printf("Failure in IoTHubDeviceTwin_CreateThermostat\n");
            }
            else
            {
              /* Set values for reported properties */
              thermostat->Config.TemperatureMeanValue = 55.5;
              thermostat->Config.TelemetryInterval = 3;
              thermostat->Device.DeviceState = "normal";
              thermostat->Device.Location.Latitude = 47.642877;
              thermostat->Device.Location.Longitude = -122.125497;
              thermostat->System.Manufacturer = "Contoso Inc.";
              thermostat->System.FirmwareVersion = "2.22";
              thermostat->System.InstalledRAM = "8 MB";
              thermostat->System.ModelNumber = "DB-14";
              thermostat->System.Platform = "Plat 9.75";
              thermostat->System.Processor = "i3-7";
              thermostat->System.SerialNumber = "SER21";
              /* Specify the signatures of the supported direct methods */
              thermostat->SupportedMethods = "{\"Reboot\": \"Reboot the device\", \"InitiateFirmwareUpdate--FwPackageURI-string\": \"Updates device Firmware. Use parameter FwPackageURI to specify the URI of the firmware file\"}";
    
              /* Send reported properties to IoT Hub */
              if (IoTHubDeviceTwin_SendReportedStateThermostat(thermostat, deviceTwinCallback, NULL) != IOTHUB_CLIENT_OK)
              {
                printf("Failed sending serialized reported state\n");
              }
              else
              {
                printf("Send DeviceInfo object to IoT Hub at startup\n");
    
                thermostat->ObjectType = "DeviceInfo";
                thermostat->IsSimulatedDevice = 0;
                thermostat->Version = "1.0";
                thermostat->DeviceProperties.HubEnabledState = 1;
                thermostat->DeviceProperties.DeviceID = (char*)deviceId;
    
                unsigned char* buffer;
                size_t bufferSize;
    
                if (SERIALIZE(&buffer, &bufferSize, thermostat->ObjectType, thermostat->Version, thermostat->IsSimulatedDevice, thermostat->DeviceProperties) != CODEFIRST_OK)
                {
                  (void)printf("Failed serializing DeviceInfo\n");
                }
                else
                {
                  sendMessage(iotHubClientHandle, buffer, bufferSize);
                }
    
                /* Send telemetry */
                thermostat->Temperature = 50;
                thermostat->ExternalTemperature = 55;
                thermostat->Humidity = 50;
                thermostat->DeviceId = (char*)deviceId;
    
                while (1)
                {
                  unsigned char*buffer;
                  size_t bufferSize;
    
                  (void)printf("Sending sensor value Temperature = %f, Humidity = %f\n", thermostat->Temperature, thermostat->Humidity);
    
                  if (SERIALIZE(&buffer, &bufferSize, thermostat->DeviceId, thermostat->Temperature, thermostat->Humidity, thermostat->ExternalTemperature) != CODEFIRST_OK)
                  {
                    (void)printf("Failed sending sensor value\r\n");
                  }
                  else
                  {
                    sendMessage(iotHubClientHandle, buffer, bufferSize);
                  }
    
                  ThreadAPI_Sleep(1000);
                }
    
                IoTHubDeviceTwin_DestroyThermostat(thermostat);
              }
            }
            IoTHubClient_Destroy(iotHubClientHandle);
          }
          serializer_deinit();
        }
      }
      platform_deinit();
    }
    

    作为参考,以下是发送到预配置解决方案的一个示例遥测消息:

    {"DeviceId":"mydevice01", "Temperature":50, "Humidity":50, "ExternalTemperature":55}
    

生成和运行示例

添加代码以调用 remote_monitoring_run 函数,然后生成并运行设备应用程序。

  1. main 函数替换为以下代码来调用 remote_monitoring_run 函数:

    int main()
    {
      remote_monitoring_run();
      return 0;
    }
    
  2. 单击“ 生成 ”,然后单击 “生成解决方案 ”以生成设备应用程序。

  3. 解决方案资源管理器中,右键单击 RMDevice 项目,单击“ 调试”,然后单击“ 启动新实例 ”以运行示例。 当应用程序将示例遥测发送到预配置解决方案时,控制台会显示消息,接收解决方案仪表板中设置的所需属性值,并响应从解决方案仪表板调用的方法。

在仪表板中查看设备遥测

借助远程监视解决方案中的仪表板,可以查看设备发送到 IoT 中心的遥测数据。

  1. 在浏览器中,返回到远程监视解决方案仪表板,单击左侧面板中 的设备 以导航到 “设备”列表

  2. “设备”列表中,应会看到设备的状态为 “正在运行”。 否则,请在“设备详细信息”面板中单击“启用设备”。

    查看设备状态

  3. 单击 “仪表板 ”返回到仪表板,在 “设备查看 ”下拉列表中选择设备以查看其遥测数据。 示例应用程序中的遥测数据是内部温度的 50 个单位,外部温度为 55 个单位,湿度为 50 个单位。

    查看设备遥测

在设备上调用方法

通过远程监视解决方案中的仪表板,可以通过 IoT 中心调用设备上的方法。 例如,在远程监视解决方案中,可以调用一种方法来模拟重启设备。

  1. 在远程监视解决方案仪表板中,单击左侧面板中 的“设备 ”以导航到 “设备”列表

  2. 单击设备 ID以选择您设备在设备列表中。

  3. “设备详细信息 ”面板中,单击“ 方法”。

    设备方法

  4. “方法 ”下拉列表中,选择 “InitiateFirmwareUpdate”,然后在 FWPACKAGEURI 中输入虚拟 URL。 单击 “调用方法 ”以调用设备上的方法。

    调用设备方法

  5. 当运行设备代码时,当设备处理该方法时,您将在控制台中看到一条消息。 方法的结果将添加到解决方案门户中的历史记录中:

    查看方法历史记录

后续步骤

自定义预配置解决方案的文章介绍了扩展此示例的一些方法。 可能的扩展包括使用实际传感器和实现其他命令。

可以了解有关 azureiotsuite.com 站点上权限的详细信息。