// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // This sample code works with the Remote Monitoring solution accelerator and shows how to: // - Report device capabilities. // - Send telemetry. // - Respond to methods, including a long-running firmware update method. // The code simulates a simple Chiller device. // CAVEAT: This sample is to demonstrate azure IoT client concepts only and is not a guide design principles or style // Checking of return codes and error values are omitted for brevity. Please practice sound engineering practices // when writing production code. #include #include #include "iothub.h" #include "iothub_device_client.h" #include "iothub_client_options.h" #include "iothub_message.h" #include "azure_c_shared_utility/threadapi.h" #include "azure_c_shared_utility/crt_abstractions.h" #include "azure_c_shared_utility/platform.h" #include "azure_c_shared_utility/shared_util_options.h" #include "iothubtransportmqtt.h" #include "parson.h" #define MESSAGERESPONSE(code, message) const char deviceMethodResponse[] = message; \ *response_size = sizeof(deviceMethodResponse) - 1; \ *response = malloc(*response_size); \ (void)memcpy(*response, deviceMethodResponse, *response_size); \ result = code; \ #define FIRMWARE_UPDATE_STATUS_VALUES \ DOWNLOADING, \ APPLYING, \ REBOOTING, \ IDLE \ /*Enumeration specifying firmware update status */ MU_DEFINE_ENUM(FIRMWARE_UPDATE_STATUS, FIRMWARE_UPDATE_STATUS_VALUES); MU_DEFINE_ENUM_STRINGS_WITHOUT_INVALID(FIRMWARE_UPDATE_STATUS, FIRMWARE_UPDATE_STATUS_VALUES); /* Paste in your device connection string */ static const char* connectionString = ""; static char msgText[1024]; static size_t g_message_count_send_confirmations = 0; static const char* initialFirmwareVersion = "1.0.0"; IOTHUB_DEVICE_CLIENT_HANDLE device_handle; // 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; // /* Converts the Chiller object into a JSON blob with reported properties ready to be sent across the wire as a twin. */ static char* serializeToJson(Chiller* chiller) { char* result = NULL; JSON_Value* root_value = json_value_init_object(); if (root_value != NULL) { JSON_Object* root_object = json_value_get_object(root_value); if (root_object != NULL) { // Only reported properties: (void)json_object_set_string(root_object, "Protocol", chiller->protocol); (void)json_object_set_string(root_object, "SupportedMethods", chiller->supportedMethods); (void)json_object_set_string(root_object, "Type", chiller->type); (void)json_object_set_string(root_object, "Firmware", chiller->firmware); (void)json_object_set_string(root_object, "FirmwareUpdateStatus", MU_ENUM_TO_STRING(FIRMWARE_UPDATE_STATUS, chiller->firmwareUpdateStatus)); (void)json_object_set_string(root_object, "Location", chiller->location); (void)json_object_set_number(root_object, "Latitude", chiller->latitude); (void)json_object_set_number(root_object, "Longitude", chiller->longitude); (void)json_object_dotset_string(root_object, "Telemetry.TemperatureSchema.MessageSchema.Name", chiller->telemetry.temperatureSchema.messageSchema.name); (void)json_object_dotset_string(root_object, "Telemetry.TemperatureSchema.MessageSchema.Format", chiller->telemetry.temperatureSchema.messageSchema.format); (void)json_object_dotset_string(root_object, "Telemetry.TemperatureSchema.MessageSchema.Fields", chiller->telemetry.temperatureSchema.messageSchema.fields); (void)json_object_dotset_string(root_object, "Telemetry.HumiditySchema.MessageSchema.Name", chiller->telemetry.humiditySchema.messageSchema.name); (void)json_object_dotset_string(root_object, "Telemetry.HumiditySchema.MessageSchema.Format", chiller->telemetry.humiditySchema.messageSchema.format); (void)json_object_dotset_string(root_object, "Telemetry.HumiditySchema.MessageSchema.Fields", chiller->telemetry.humiditySchema.messageSchema.fields); (void)json_object_dotset_string(root_object, "Telemetry.PressureSchema.MessageSchema.Name", chiller->telemetry.pressureSchema.messageSchema.name); (void)json_object_dotset_string(root_object, "Telemetry.PressureSchema.MessageSchema.Format", chiller->telemetry.pressureSchema.messageSchema.format); (void)json_object_dotset_string(root_object, "Telemetry.PressureSchema.MessageSchema.Fields", chiller->telemetry.pressureSchema.messageSchema.fields); } result = json_serialize_to_string(root_value); json_value_free(root_value); } return result; } static void connection_status_callback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void* user_context) { (void)reason; (void)user_context; // This sample DOES NOT take into consideration network outages. if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED) { (void)printf("The device client is connected to iothub\r\n"); } else { (void)printf("The device client has been disconnected\r\n"); } } static void send_confirm_callback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback) { (void)userContextCallback; g_message_count_send_confirmations++; (void)printf("Confirmation callback received for message %zu with result %s\r\n", g_message_count_send_confirmations, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result)); } static void reported_state_callback(int status_code, void* userContextCallback) { (void)userContextCallback; (void)printf("Device Twin reported properties update completed with result: %d\r\n", status_code); } static void sendChillerReportedProperties(Chiller* chiller) { if (chiller != NULL && device_handle != NULL) { char* reportedProperties = serializeToJson(chiller); if (reportedProperties) { (void)IoTHubDeviceClient_SendReportedState(device_handle, (const unsigned char*)reportedProperties, strlen(reportedProperties), reported_state_callback, NULL); free(reportedProperties); } } } // /* 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; } // void getFirmwareUpdateValues(Chiller* chiller, const unsigned char* payload) { if (chiller != NULL) { free(chiller->new_firmware_version); free(chiller->new_firmware_URI); chiller->new_firmware_URI = NULL; chiller->new_firmware_version = NULL; JSON_Value* root_value = json_parse_string((char*)payload); JSON_Object* root_object = json_value_get_object(root_value); JSON_Value* newFirmwareVersion = json_object_get_value(root_object, "Firmware"); if (newFirmwareVersion != NULL) { const char* data = json_value_get_string(newFirmwareVersion); if (data != NULL) { size_t size = strlen(data) + 1; chiller->new_firmware_version = malloc(size); if (chiller->new_firmware_version != NULL) { (void)memcpy(chiller->new_firmware_version, data, size); } } } JSON_Value* newFirmwareURI = json_object_get_value(root_object, "FirmwareUri"); if (newFirmwareURI != NULL) { const char* data = json_value_get_string(newFirmwareURI); if (data != NULL) { size_t size = strlen(data) + 1; chiller->new_firmware_URI = malloc(size); if (chiller->new_firmware_URI != NULL) { (void)memcpy(chiller->new_firmware_URI, data, size); } } } // Free resources json_value_free(root_value); } } // 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; } // // 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); } } // //
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; } //