将设备连接到远程监视预配置解决方案 (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. 在“解决方案资源管理器”中,右键单击e RMDevice 项目,并单击“管理 NuGet 包”。 单击“浏览”,搜索并安装以下 NuGet 包:

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

  7. 单击 Linker 文件夹,并单击“输入”属性页。

  8. crypt32.lib 添加到“其他依赖项”属性。 单击“确定”,并再次单击“确定”以保存项目属性值。

将 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 语句之后添加以下变量声明。 将占位符值 [Device ID] 和 [Device Key] 替换为在远程监视解决方案仪表板中记下的设备值。 使用解决方案仪表板中的 IoT 中心主机名替换 [IoTHub Name]。 例如,如果 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 命名空间。
    • 使用设备连接字符串初始化客户端。
    • 创建 Thermostat 模型的实例。
    • 创建并发送报告属性值。
    • 发送 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 站点权限