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

方案概述

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

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

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

该设备还能响应从解决方案仪表板中调用的方法,以及在解决方案仪表板中设置的所需属性值。

要完成此教程,需要一个有效的 Azure 帐户。 如果没有帐户,可以创建一个试用帐户,只需几分钟即可完成。 有关详细信息,请参阅 Azure 试用

开始之前

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

预配远程监视预配置解决方案

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

  1. https://www.azureiotsuite.cn/ 页上,单击“+”创建解决方案。

  2. 在“远程监视”面板上,单击“选择”创建解决方案。

  3. 在“创建远程监视解决方案”页上,输入所选的解决方案名称,选择要部署到的区域,然后选择要使用的 Azure 订阅。 。
  4. 等待预配过程完成。

Warning

预配置的解决方案使用可计费的 Azure 服务。 当使用完预配置的解决方案之后,请务必将它从订阅中删除,以避免产生任何不必要的费用。 只需访问 https://www.azureiotsuite.cn/ 页,即可将预配置的解决方案从订阅中完全删除。

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

解决方案仪表板

在远程监视方案中预配设备

Note

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

连接到预配置解决方案的设备必须能够使用有效凭据对 IoT 中心识别自身。 用户可从解决方案仪表板中检索设备凭据。 本教程后文中的客户端应用程序要采用该设备凭据。

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

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

    添加设备

  2. 在“自定义设备”面板中,单击“新增”。

    添加自定义设备

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

    添加设备 ID

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

    查看设备凭据

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

生成并运行示例 C 客户端 Linux

以下步骤说明如何创建一个客户端应用程序来与远程监控预配置解决方案通信。 此应用程序以 C 编写,在 Ubuntu Linux 上生成和运行。

要完成这些步骤,需要一个运行 Ubuntu 版本 15.04 或 15.10 的设备。 继续操作之前,请使用以下命令在 Ubuntu 设备上安装必备组件包:

sudo apt-get install cmake gcc g++

在设备上安装客户端库

Azure IoT 中心客户端库以包的形式提供,可以使用 apt get 命令在 Ubuntu 设备上安装该包。 完成以下步骤,在 Ubuntu 计算机上安装包含 IoT 中心客户端库和标头文件的包:

  1. 在外壳程序中,向计算机添加 AzureIoT 存储库:

    sudo add-apt-repository ppa:aziotsdklinux/ppa-azureiot
    sudo apt-get update
    
  2. 安装 azure-iot-sdk-c-dev 包

    sudo apt-get install -y azure-iot-sdk-c-dev
    

安装 Parson JSON 分析器

IoT 中心客户端库使用 Parson JSON 分析器分析消息有效负载。 在计算机上的适当文件夹中,使用以下命令克隆 Parson GitHub 存储库:

git clone https://github.com/kgabis/parson.git

准备项目

在 Ubuntu 计算机上,创建名为“remote _monitoring”的文件夹。 在“remote _monitoring”文件夹中执行以下操作:

  • 创建四个文件:main.c、remote_monitoring.c、remote_monitoring.h、CMakeLists.txt。
  • 创建名为 parson的文件夹。

将“parson.c”和“parson.h”文件从 Parson 存储库的本地副本复制到“remote_monitoring/parson”文件夹。

在文本编辑器中,打开“remote_monitoring.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.cn,则将 [IoTHub Name] 替换为 contoso

    static const char* deviceId = "[Device Id]";
    static const char* connectionString = "HostName=[IoTHub Name].azure-devices.cn;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 specifiy 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}
      

调用远程 _monitoring_ 函数

在文件编辑器中,打开“remote_monitoring.h”文件。 添加以下代码:

void remote_monitoring_run(void);

在文本编辑器中,打开 main.c 文件。添加以下代码:

#include "remote_monitoring.h"

int main(void)
{
    remote_monitoring_run();

    return 0;
}

生成并运行应用程序

以下步骤描述如何使用 CMake 生成客户端应用程序。

  1. 在文本编辑器中,打开“remote_monitoring”文件夹中的“CMakeLists.txt”文件。

  2. 添加以下指令,以定义如何生成客户端应用程序:

    macro(compileAsC99)
      if (CMAKE_VERSION VERSION_LESS "3.1")
        if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
          set (CMAKE_C_FLAGS "--std=c99 ${CMAKE_C_FLAGS}")
          set (CMAKE_CXX_FLAGS "--std=c++11 ${CMAKE_CXX_FLAGS}")
        endif()
      else()
        set (CMAKE_C_STANDARD 99)
        set (CMAKE_CXX_STANDARD 11)
      endif()
    endmacro(compileAsC99)
    
    cmake_minimum_required(VERSION 2.8.11)
    compileAsC99()
    
    set(AZUREIOT_INC_FOLDER "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/parson" "/usr/include/azureiot" "/usr/include/azureiot/inc")
    
    include_directories(${AZUREIOT_INC_FOLDER})
    
    set(sample_application_c_files
        ./parson/parson.c
        ./remote_monitoring.c
        ./main.c
    )
    
    set(sample_application_h_files
        ./parson/parson.h
        ./remote_monitoring.h
    )
    
    add_executable(sample_app ${sample_application_c_files} ${sample_application_h_files})
    
    target_link_libraries(sample_app
        serializer
        iothub_client
        iothub_client_mqtt_transport
        aziotsharedutil
        umqtt
        pthread
        curl
        ssl
        crypto
        m
    )
    
  3. 在“remote_monitoring”文件夹中,创建一个文件夹用于存储 CMake 生成的“make”文件,然后运行“cmake”和“make”命令,如下所示:

    mkdir cmake
    cd cmake
    cmake ../
    make
    
  4. 运行客户端应用程序,并将遥测数据发送到 IoT 中心:

    ./sample_app
    

在仪表板中查看设备遥测数据

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

  1. 在浏览器中,返回到远程监视解决方案仪表板,单击左侧面板中的“设备”导航到“设备列表”。
  2. 在“设备列表”中,应会看到设备状态为“正在运行”。 如果不是,请在“设备详细信息”面板中单击“启用设备”。

    查看服务状态

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

    查看设备遥测数据

在设备上调用方法

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

  1. 在远程监视解决方案仪表板中,单击左侧面板中的“设备”导航到“设备列表”。
  2. 在“设备列表”中,单击设备的“设备 ID”。
  3. 在“设备详细信息”面板中,单击“方法”。

    设备方法

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

    调用设备方法

  5. 设备处理方法时,运行设备代码的控制台中会显示消息。 方法的结果将添加到解决方案门户中的历史记录:

    查看方法历史记录

后续步骤

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

详细了解 azureiotsuite.cn 站点权限