Подключение устройства к акселератору решения для удаленного мониторинга в Windows
В этом руководстве вы реализуете устройство Chiller, которое отправляет следующие данные телеметрии в акселератор решений для удаленного мониторинга:
- температура;
- Давление
- влажность.
Чтобы упростить задачу, при помощи кода создаются примеры значений телеметрии для Chiller. Вы можете расширить пример, подключив к устройству физические датчики, которые будут отправлять реальные данные телеметрии.
Устройство в примере также:
- отправляет в решение метаданные для описания его возможностей;
- реагирует на действия, активированные на странице решения Устройства;
- реагирует на изменения конфигурации, сведения о которых отправляются со страницы решения Устройства.
Для работы с этим руководством требуется активная учетная запись Azure. Если ее нет, можно создать бесплатную пробную учетную запись всего за несколько минут. Дополнительные сведения см. в разделе Бесплатная пробная версия Azure.
Перед началом работы
Прежде чем писать код для устройства, разверните акселератор решения для удаленного мониторинга и добавьте в решение новое реальное устройство.
Развертывание акселератора решения для удаленного мониторинга
Созданное в этом руководстве устройство Chiller отправляет данные в экземпляр акселератора решений для удаленного мониторинга. Если вы еще не подготовили этот акселератор решений в своей учетной записи Azure, изучите статью Развертывание акселератора решения для удаленного мониторинга.
После завершения развертывания решения для удаленного мониторинга нажмите кнопку Запустить, чтобы открыть панель мониторинга этого решения в браузере.
Добавление устройства в решение для удаленного мониторинга
Примечание
Если вы уже добавили устройство в решение, пропустите этот шаг. Однако для следующего шага требуется строка подключения вашего устройства. Вы можете получить строку подключения устройства на портале Azure или с помощью средства CLI az iot.
Чтобы устройство смогло подключиться к акселератору решений, оно должно пройти идентификацию в Центре Интернета вещей с использованием допустимых учетных данных. При добавлении устройства в решение есть возможность сохранить строку подключения устройства, содержащую эти учетные данные. Вы добавите эту строку подключения в клиентское приложение далее в этом руководстве.
Чтобы добавить устройство в решение для удаленного мониторинга, выполните следующие действия на странице решения Обозреватель устройств:
Щелкните + Новое устройство, а затем для параметра Тип устройства выберите значение Реальное:
Введите значение Physical-chiller в качестве идентификатора устройства. Выберите параметры Симметричный ключ и Автоматически создавать ключи:
Нажмите кнопку Применить. Затем запишите значения параметров Идентификатор устройства, Первичный ключ и Строка подключения — первичный ключ:
Вы добавили реальное устройство в акселератор решения для удаленного мониторинга и записали его строку подключения. В следующих разделах реализуется клиентское приложение, которое использует строку подключения устройства для подключения к решению.
Клиентское приложение реализует встроенную модель устройства 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;
}
Сборка и запуск примера
Измените файл remote_monitoring.c, чтобы заменить
<connectionstring>
строкой подключения устройства, которую вы записали в начале этого практического руководства, при добавлении устройства в акселератор решений.Выполните действия, описанные в сборке пакета SDK для C в Windows, чтобы выполнить сборку пакета SDK и клиентского приложения удаленного мониторинга.
В командной строке, используемой для построения решения, выполните следующую команду.
samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
В консоли появляются сообщения в следующих случаях:
- приложение отправляет пример данных телеметрии в акселератор решения;
- приложение отвечает на методы, вызываемые из панели мониторинга решения.
Просмотр телеметрии устройства
Вы можете просмотреть данные телеметрии, отправленные с устройства, на странице Обозреватель устройств в решении.
Выберите подготовленное устройство в списке устройств на странице Обозреватель устройств. На панели отображаются сведения об устройстве, включая график телеметрии устройства:
Выберите Pressure (Давление) для изменения порядка отображения телеметрии:
Чтобы просмотреть диагностические сведения об устройстве, прокрутите вниз до раздела Diagnostics (Диагностика):
Действия на устройстве
Для вызова методов на устройствах используйте страницу Обозреватель устройств в решении для удаленного мониторинга. Например, в устройствах решения для удаленного мониторинга Chiller реализован метод перезагрузки.
Выберите Устройства для перехода к странице Обозреватель устройств в решении.
Выберите подготовленное устройство в списке устройств на странице Обозреватель устройств:
Чтобы отобразить список методов, которые можно вызвать на устройстве, выберите Задания > Методы. Чтобы запланировать выполнение задания на нескольких устройствах, можно выбрать несколько устройств в списке. На панели Jobs (Задания) отображаются типы метода, который является общим для всех выбранных устройств.
Выберите Перезагрузка, задайте имя задания RebootPhysicalChiller и щелкните Применить:
Когда имитированное устройство обрабатывает метод, в консоли отображается последовательность сообщений о выполнении кода устройства.
Примечание
Для отслеживания состояния задания в решении выберите Просмотреть состояние задания.
Дальнейшие действия
Способы настройки акселератора решений описаны в статье Настройка акселератора решения для удаленного мониторинга.