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

场景概述

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

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

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

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

若要完成本教程,需要一个有效的 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. 在解决方案仪表板的设备列表中选择设备。 然后,在 设备详细信息 面板中,单击 启用设备。 设备当前状态为 运行。 远程监视解决方案现在可以从设备接收遥测数据,并在设备上调用方法。

生成并运行示例 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. 在 shell 中,将 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.cremote_monitoring.cremote_monitoring.hCMakeLists.txt
  • 创建名为 parson 的文件夹。

parson.cparson.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 添加以下变量声明。 将占位符值 [设备 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函数

在文本编辑器中,打开 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 文件,然后运行 cmakemake 命令如下所示:

    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.com 站点上权限的详细信息。