Поделиться через


Подключение устройства к акселератору решения для удаленного мониторинга в Windows

В этом руководстве вы реализуете устройство Chiller, которое отправляет следующие данные телеметрии в акселератор решений для удаленного мониторинга:

  • температура;
  • Давление
  • влажность.

Чтобы упростить задачу, при помощи кода создаются примеры значений телеметрии для Chiller. Вы можете расширить пример, подключив к устройству физические датчики, которые будут отправлять реальные данные телеметрии.

Устройство в примере также:

  • отправляет в решение метаданные для описания его возможностей;
  • реагирует на действия, активированные на странице решения Устройства;
  • реагирует на изменения конфигурации, сведения о которых отправляются со страницы решения Устройства.

Для работы с этим руководством требуется активная учетная запись Azure. Если ее нет, можно создать бесплатную пробную учетную запись всего за несколько минут. Дополнительные сведения см. в разделе Бесплатная пробная версия Azure.

Перед началом работы

Прежде чем писать код для устройства, разверните акселератор решения для удаленного мониторинга и добавьте в решение новое реальное устройство.

Развертывание акселератора решения для удаленного мониторинга

Созданное в этом руководстве устройство Chiller отправляет данные в экземпляр акселератора решений для удаленного мониторинга. Если вы еще не подготовили этот акселератор решений в своей учетной записи Azure, изучите статью Развертывание акселератора решения для удаленного мониторинга.

После завершения развертывания решения для удаленного мониторинга нажмите кнопку Запустить, чтобы открыть панель мониторинга этого решения в браузере.

Панель мониторинга решения

Добавление устройства в решение для удаленного мониторинга

Примечание

Если вы уже добавили устройство в решение, пропустите этот шаг. Однако для следующего шага требуется строка подключения вашего устройства. Вы можете получить строку подключения устройства на портале Azure или с помощью средства CLI az iot.

Чтобы устройство смогло подключиться к акселератору решений, оно должно пройти идентификацию в Центре Интернета вещей с использованием допустимых учетных данных. При добавлении устройства в решение есть возможность сохранить строку подключения устройства, содержащую эти учетные данные. Вы добавите эту строку подключения в клиентское приложение далее в этом руководстве.

Чтобы добавить устройство в решение для удаленного мониторинга, выполните следующие действия на странице решения Обозреватель устройств:

  1. Щелкните + Новое устройство, а затем для параметра Тип устройства выберите значение Реальное:

    Добавление реального устройства

  2. Введите значение Physical-chiller в качестве идентификатора устройства. Выберите параметры Симметричный ключ и Автоматически создавать ключи:

    Выбор параметров устройства

  3. Нажмите кнопку Применить. Затем запишите значения параметров Идентификатор устройства, Первичный ключ и Строка подключения — первичный ключ:

    Получение учетных данных

Вы добавили реальное устройство в акселератор решения для удаленного мониторинга и записали его строку подключения. В следующих разделах реализуется клиентское приложение, которое использует строку подключения устройства для подключения к решению.

Клиентское приложение реализует встроенную модель устройства Chiller. Модель устройства с акселератором решений указывает следующие данные устройства:

  • Сведения о свойствах, которые устройство передает в решение. Например, устройство Chiller передает сведения о своей микропрограмме и расположении.
  • Сведения о типах данных телеметрии, которые устройство отправляет в решение. Например, устройство Chiller отправляет показатели температуры, влажности и давления.
  • Сведения о методах, запуск которых можно запланировать на устройстве при помощи решения. Например, устройство Chiller должно реализовать методы Reboot, FirmwareUpdate, EmergencyValveRelease и IncreasePressure.

В этом руководстве показано, как подключить реальное устройство к акселератору решения для удаленного мониторинга.

Как и для большинства внедряемых приложений, работающих на устройствах с ограниченными ресурсами, клиентский код для приложения на устройстве пишется на языке C. В этом руководстве вы создадите приложение на компьютере под управлением Windows.

Если вы предпочитаете имитацию устройства, см. раздел Создание и тестирование нового имитированного устройства.

Предварительные требования

Чтобы завершить действия, описанные в этом практическом руководстве, выполните описанные в настройках среды разработки Windows, чтобы добавить необходимые средства разработки и библиотеки на компьютер Windows.

Просмотрите код

Пример кода, используемый в этом руководстве доступный в репозитории GitHub пакета SDK для устройств Azure IoT для C.

Загрузка исходного кода и подготовка проекта

Чтобы подготовить проект, клонируйте репозиторий пакетов SDK для устройств Azure IoT для C из GitHub.

Этот пример находится в папке samples/solutions/remote_monitoring_client.

В текстовом редакторе откройте файл remote_monitoring.c в папке samples/solutions/remote_monitoring_client.

Пошаговое руководство по написанию кода

В этом разделе описаны основные части примера кода и объясняется, как они связаны с акселератором решения для удаленного мониторинга.

В следующем фрагменте показано, как определяются сообщаемые свойства, описывающие возможности устройства. Эти свойства включают в себя:

  • Расположение устройства для включения акселератора решений, чтобы добавить устройство для сопоставления.
  • Текущая версия встроенного ПО.
  • Список методов, которые поддерживает устройство.
  • Схема сообщений телеметрии, отправляемых устройством.
typedef struct MESSAGESCHEMA_TAG
{
    char* name;
    char* format;
    char* fields;
} MessageSchema;

typedef struct TELEMETRYSCHEMA_TAG
{
    MessageSchema messageSchema;
} TelemetrySchema;

typedef struct TELEMETRYPROPERTIES_TAG
{
    TelemetrySchema temperatureSchema;
    TelemetrySchema humiditySchema;
    TelemetrySchema pressureSchema;
} TelemetryProperties;

typedef struct CHILLER_TAG
{
    // Reported properties
    char* protocol;
    char* supportedMethods;
    char* type;
    char* firmware;
    FIRMWARE_UPDATE_STATUS firmwareUpdateStatus;
    char* location;
    double latitude;
    double longitude;
    TelemetryProperties telemetry;

    // Manage firmware update process
    char* new_firmware_version;
    char* new_firmware_URI;
} Chiller;

Пример включает функцию serializeToJson, которая выполняет сериализацию структуры данных, с помощью библиотеки Parson.

Пример включает несколько функций обратного вызова, которые выводят сведения на консоль в том случае,когда клиент взаимодействует с акселератором решений:

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

Эта функция обратного вызова device_method_callback показана в примере кода ниже. Эта функция определяет действие, выполняемое при получении вызова метода из комплекта акселератора решений. Функция получает ссылку на структуру данных Chiller в параметре userContextCallback. Значение userContextCallback устанавливается, когда функция обратного вызова настраивается в основной функции.

static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* response_size, void* userContextCallback)
{
    Chiller *chiller = (Chiller *)userContextCallback;

    int result;

    (void)printf("Direct method name:    %s\r\n", method_name);

    (void)printf("Direct method payload: %.*s\r\n", (int)size, (const char*)payload);

    if (strcmp("Reboot", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Rebooting\" }")
    }
    else if (strcmp("EmergencyValveRelease", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Releasing emergency valve\" }")
    }
    else if (strcmp("IncreasePressure", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Increasing pressure\" }")
    }
    else if (strcmp("FirmwareUpdate", method_name) == 0)
    {
        if (chiller->firmwareUpdateStatus != IDLE)
        {
            (void)printf("Attempt to invoke firmware update out of order\r\n");
            MESSAGERESPONSE(400, "{ \"Response\": \"Attempting to initiate a firmware update out of order\" }")
        }
        else
        {
            getFirmwareUpdateValues(chiller, payload);

            if (chiller->new_firmware_version != NULL && chiller->new_firmware_URI != NULL)
            {
                // Create a thread for the long-running firmware update process.
                THREAD_HANDLE thread_apply;
                THREADAPI_RESULT t_result = ThreadAPI_Create(&thread_apply, do_firmware_update, chiller);
                if (t_result == THREADAPI_OK)
                {
                    (void)printf("Starting firmware update thread\r\n");
                    MESSAGERESPONSE(201, "{ \"Response\": \"Starting firmware update thread\" }")
                }
                else
                {
                    (void)printf("Failed to start firmware update thread\r\n");
                    MESSAGERESPONSE(500, "{ \"Response\": \"Failed to start firmware update thread\" }")
                }
            }
            else
            {
                (void)printf("Invalid method payload\r\n");
                MESSAGERESPONSE(400, "{ \"Response\": \"Invalid payload\" }")
            }
        }
    }
    else
    {
        // All other entries are ignored.
        (void)printf("Method not recognized\r\n");
        MESSAGERESPONSE(400, "{ \"Response\": \"Method not recognized\" }")
    }

    return result;
}

Когда акселератор решения вызывает метод обновления встроенного ПО, пример десериализует полезные данные JSON и запускает фоновый поток для завершения процесса обновления. В следующем фрагменте представлен do_firmware_update, который выполняется в потоке.

/*
 This is a thread allocated to process a long-running device method call.
 It uses device twin reported properties to communicate status values
 to the Remote Monitoring solution accelerator.
*/
static int do_firmware_update(void *param)
{
    Chiller *chiller = (Chiller *)param;
    printf("Running simulated firmware update: URI: %s, Version: %s\r\n", chiller->new_firmware_URI, chiller->new_firmware_version);

    printf("Simulating download phase...\r\n");
    chiller->firmwareUpdateStatus = DOWNLOADING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating apply phase...\r\n");
    chiller->firmwareUpdateStatus = APPLYING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating reboot phase...\r\n");
    chiller->firmwareUpdateStatus = REBOOTING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    size_t size = strlen(chiller->new_firmware_version) + 1;
    (void)memcpy(chiller->firmware, chiller->new_firmware_version, size);

    chiller->firmwareUpdateStatus = IDLE;
    sendChillerReportedProperties(chiller);

    return 0;
}

В следующем фрагменте показано, как клиент отправляет сообщения телеметрии в акселератор решений. Свойства сообщения включают схему сообщений, чтобы дать возможность акселератору решений отобразить телеметрию на панели мониторинга:

static void send_message(IOTHUB_DEVICE_CLIENT_HANDLE handle, char* message, char* schema)
{
    IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(message);
    if (message_handle != NULL)
    {
        // Set system properties
        (void)IoTHubMessage_SetMessageId(message_handle, "MSG_ID");
        (void)IoTHubMessage_SetCorrelationId(message_handle, "CORE_ID");
        (void)IoTHubMessage_SetContentTypeSystemProperty(message_handle, "application%2fjson");
        (void)IoTHubMessage_SetContentEncodingSystemProperty(message_handle, "utf-8");

        // Set application properties
        MAP_HANDLE propMap = IoTHubMessage_Properties(message_handle);
        (void)Map_AddOrUpdate(propMap, "$$MessageSchema", schema);
        (void)Map_AddOrUpdate(propMap, "$$ContentType", "JSON");

        time_t now = time(0);
        struct tm* timeinfo;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996) /* Suppress warning about possible unsafe function in Visual Studio */
#endif
        timeinfo = gmtime(&now);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
        char timebuff[50];
        strftime(timebuff, 50, "%Y-%m-%dT%H:%M:%SZ", timeinfo);
        (void)Map_AddOrUpdate(propMap, "$$CreationTimeUtc", timebuff);

        IoTHubDeviceClient_SendEventAsync(handle, message_handle, send_confirm_callback, NULL);

        IoTHubMessage_Destroy(message_handle);
    }
}

Основная функция в примере:

  • Инициализирует и завершает работу подсистемы пакета SDK.
  • Инициализирует структуру данных Chiller.
  • Отправляет сообщаемые свойства в акселератор решений.
  • Настраивает функцию метода обратного вызова устройства.
  • Отправляет смоделированные значения телеметрии в акселератор решений.
int main(void)
{
    srand((unsigned int)time(NULL));
    double minTemperature = 50.0;
    double minPressure = 55.0;
    double minHumidity = 30.0;
    double temperature = 0;
    double pressure = 0;
    double humidity = 0;

    (void)printf("This sample simulates a Chiller device connected to the Remote Monitoring solution accelerator\r\n\r\n");

    // Used to initialize sdk subsystem
    (void)IoTHub_Init();

    (void)printf("Creating IoTHub handle\r\n");
    // Create the iothub handle here
    device_handle = IoTHubDeviceClient_CreateFromConnectionString(connectionString, MQTT_Protocol);
    if (device_handle == NULL)
    {
        (void)printf("Failure creating IotHub device. Hint: Check your connection string.\r\n");
    }
    else
    {
        // Setting connection status callback to get indication of connection to iothub
        (void)IoTHubDeviceClient_SetConnectionStatusCallback(device_handle, connection_status_callback, NULL);

        Chiller chiller;
        memset(&chiller, 0, sizeof(Chiller));
        chiller.protocol = "MQTT";
        chiller.supportedMethods = "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure";
        chiller.type = "Chiller";
        size_t size = strlen(initialFirmwareVersion) + 1;
        chiller.firmware = malloc(size);
        if (chiller.firmware == NULL)
        {
            (void)printf("Chiller Firmware failed to allocate memory.\r\n");
        }
        else
        {
            memcpy(chiller.firmware, initialFirmwareVersion, size);
            chiller.firmwareUpdateStatus = IDLE;
            chiller.location = "Building 44";
            chiller.latitude = 47.638928;
            chiller.longitude = -122.13476;
            chiller.telemetry.temperatureSchema.messageSchema.name = "chiller-temperature;v1";
            chiller.telemetry.temperatureSchema.messageSchema.format = "JSON";
            chiller.telemetry.temperatureSchema.messageSchema.fields = "{\"temperature\":\"Double\",\"temperature_unit\":\"Text\"}";
            chiller.telemetry.humiditySchema.messageSchema.name = "chiller-humidity;v1";
            chiller.telemetry.humiditySchema.messageSchema.format = "JSON";
            chiller.telemetry.humiditySchema.messageSchema.fields = "{\"humidity\":\"Double\",\"humidity_unit\":\"Text\"}";
            chiller.telemetry.pressureSchema.messageSchema.name = "chiller-pressure;v1";
            chiller.telemetry.pressureSchema.messageSchema.format = "JSON";
            chiller.telemetry.pressureSchema.messageSchema.fields = "{\"pressure\":\"Double\",\"pressure_unit\":\"Text\"}";

            sendChillerReportedProperties(&chiller);

            (void)IoTHubDeviceClient_SetDeviceMethodCallback(device_handle, device_method_callback, &chiller);

            while (1)
            {
                temperature = minTemperature + ((double)(rand() % 10) + 5);
                pressure = minPressure + ((double)(rand() % 10) + 5);
                humidity = minHumidity + ((double)(rand() % 20) + 5);

                if (chiller.firmwareUpdateStatus == IDLE)
                {
                    (void)printf("Sending sensor value Temperature = %f %s,\r\n", temperature, "F");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"temperature\":%.2f,\"temperature_unit\":\"F\"}", temperature);
                    send_message(device_handle, msgText, chiller.telemetry.temperatureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Pressure = %f %s,\r\n", pressure, "psig");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"pressure\":%.2f,\"pressure_unit\":\"psig\"}", pressure);
                    send_message(device_handle, msgText, chiller.telemetry.pressureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Humidity = %f %s,\r\n", humidity, "%");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"humidity\":%.2f,\"humidity_unit\":\"%%\"}", humidity);
                    send_message(device_handle, msgText, chiller.telemetry.humiditySchema.messageSchema.name);
                }

                ThreadAPI_Sleep(5000);
            }

            (void)printf("\r\nShutting down\r\n");

            // Clean up the iothub sdk handle and free resources
            IoTHubDeviceClient_Destroy(device_handle);
            free(chiller.firmware);
            free(chiller.new_firmware_URI);
            free(chiller.new_firmware_version);
        }
    }
    // Shutdown the sdk subsystem
    IoTHub_Deinit();

    return 0;
}

Сборка и запуск примера

  1. Измените файл remote_monitoring.c, чтобы заменить <connectionstring> строкой подключения устройства, которую вы записали в начале этого практического руководства, при добавлении устройства в акселератор решений.

  2. Выполните действия, описанные в сборке пакета SDK для C в Windows, чтобы выполнить сборку пакета SDK и клиентского приложения удаленного мониторинга.

  3. В командной строке, используемой для построения решения, выполните следующую команду.

    samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
    

    В консоли появляются сообщения в следующих случаях:

    • приложение отправляет пример данных телеметрии в акселератор решения;
    • приложение отвечает на методы, вызываемые из панели мониторинга решения.

Просмотр телеметрии устройства

Вы можете просмотреть данные телеметрии, отправленные с устройства, на странице Обозреватель устройств в решении.

  1. Выберите подготовленное устройство в списке устройств на странице Обозреватель устройств. На панели отображаются сведения об устройстве, включая график телеметрии устройства:

    Просмотр сведений об устройстве

  2. Выберите Pressure (Давление) для изменения порядка отображения телеметрии:

    Просмотр телеметрии по давлению

  3. Чтобы просмотреть диагностические сведения об устройстве, прокрутите вниз до раздела Diagnostics (Диагностика):

    Просмотр диагностики устройства

Действия на устройстве

Для вызова методов на устройствах используйте страницу Обозреватель устройств в решении для удаленного мониторинга. Например, в устройствах решения для удаленного мониторинга Chiller реализован метод перезагрузки.

  1. Выберите Устройства для перехода к странице Обозреватель устройств в решении.

  2. Выберите подготовленное устройство в списке устройств на странице Обозреватель устройств:

    Выбор реального устройства

  3. Чтобы отобразить список методов, которые можно вызвать на устройстве, выберите Задания > Методы. Чтобы запланировать выполнение задания на нескольких устройствах, можно выбрать несколько устройств в списке. На панели Jobs (Задания) отображаются типы метода, который является общим для всех выбранных устройств.

  4. Выберите Перезагрузка, задайте имя задания RebootPhysicalChiller и щелкните Применить:

    Планирование обновления встроенного ПО

  5. Когда имитированное устройство обрабатывает метод, в консоли отображается последовательность сообщений о выполнении кода устройства.

Примечание

Для отслеживания состояния задания в решении выберите Просмотреть состояние задания.

Дальнейшие действия

Способы настройки акселератора решений описаны в статье Настройка акселератора решения для удаленного мониторинга.