AzureIoTMessageWithProperties/src/HighLevelApp/common/cloud.c (201 lines of code) (raw):
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
#include <memory.h>
#include <stdlib.h>
#include <applibs/eventloop.h>
#include <applibs/log.h>
#include "parson.h"
#include "azure_iot.h"
#include "cloud.h"
#include "exitcodes.h"
#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
// This file implements the interface described in cloud.h in terms of an Azure IoT Hub.
// Specifically, it translates Azure IoT Hub specific concepts (events, device twin messages, device
// methods, etc) into business domain concepts (telemetry, upload enabled, alarm raised)
static const char azureSphereModelId[] = "dtmi:com:example:azuresphere:thermometer;1";
static MESSAGE_PROPERTY* telemetryMessageProperties[] = {
&(MESSAGE_PROPERTY) { .key = "appid", .value = "hvac" },
&(MESSAGE_PROPERTY) {.key = "format", .value = "json" },
&(MESSAGE_PROPERTY) {.key = "type", .value = "telemetry" },
&(MESSAGE_PROPERTY) {.key = "version", .value = "1" }
};
// Azure IoT Hub callback handlers
static void DeviceTwinCallbackHandler(const char *nullTerminatedJsonString);
static int DeviceMethodCallbackHandler(const char *methodName, const unsigned char *payload,
size_t payloadSize, unsigned char **response,
size_t *responseSize);
static void ConnectionChangedCallbackHandler(bool connected);
// Default handlers for cloud events
static void DefaultTelemetryUploadEnabledChangedHandler(bool uploadEnabled);
static void DefaultDisplayAlertHandler(const char *alertMessage);
static void DefaultConnectionChangedHandler(bool connected);
// Cloud event callback handlers
static Cloud_TelemetryUploadEnabledChangedCallbackType
thermometerTelemetryUploadEnabledChangedCallbackFunction =
DefaultTelemetryUploadEnabledChangedHandler;
static Cloud_DisplayAlertCallbackType displayAlertCallbackFunction = DefaultDisplayAlertHandler;
static Cloud_ConnectionChangedCallbackType connectionChangedCallbackFunction =
DefaultConnectionChangedHandler;
// Utility functions
static Cloud_Result AzureIoTToCloudResult(AzureIoT_Result result);
// Constants
#define MAX_PAYLOAD_SIZE 512
unsigned int latestVersion = 1;
ExitCode Cloud_Initialize(EventLoop *el, void *backendContext,
ExitCode_CallbackType failureCallback,
Cloud_TelemetryUploadEnabledChangedCallbackType
thermometerTelemetryUploadEnabledChangedCallback,
Cloud_DisplayAlertCallbackType displayAlertCallback,
Cloud_ConnectionChangedCallbackType connectionChangedCallback)
{
if (thermometerTelemetryUploadEnabledChangedCallback != NULL) {
thermometerTelemetryUploadEnabledChangedCallbackFunction =
thermometerTelemetryUploadEnabledChangedCallback;
}
if (displayAlertCallback != NULL) {
displayAlertCallbackFunction = displayAlertCallback;
}
if (connectionChangedCallback != NULL) {
connectionChangedCallbackFunction = connectionChangedCallback;
}
AzureIoT_Callbacks callbacks = {
.connectionStatusCallbackFunction = ConnectionChangedCallbackHandler,
.deviceTwinReceivedCallbackFunction = DeviceTwinCallbackHandler,
.deviceTwinReportStateAckCallbackTypeFunction = NULL,
.sendTelemetryCallbackFunction = NULL,
.deviceMethodCallbackFunction = DeviceMethodCallbackHandler};
return AzureIoT_Initialize(el, failureCallback, azureSphereModelId, backendContext, callbacks);
}
void Cloud_Cleanup(void)
{
AzureIoT_Cleanup();
}
static Cloud_Result AzureIoTToCloudResult(AzureIoT_Result result)
{
switch (result) {
case AzureIoT_Result_OK:
return Cloud_Result_OK;
case AzureIoT_Result_NoNetwork:
return Cloud_Result_NoNetwork;
case AzureIoT_Result_OtherFailure:
default:
return Cloud_Result_OtherFailure;
}
}
Cloud_Result Cloud_SendTelemetry(const Cloud_Telemetry *telemetry)
{
JSON_Value *telemetryValue = json_value_init_object();
JSON_Object *telemetryRoot = json_value_get_object(telemetryValue);
json_object_dotset_number(telemetryRoot, "temperature", telemetry->temperature);
char *serializedTelemetry = json_serialize_to_string(telemetryValue);
AzureIoT_Result aziotResult = AzureIoT_SendTelemetryWithProperties(serializedTelemetry, NULL, telemetryMessageProperties , NELEMS(telemetryMessageProperties));
Cloud_Result result = AzureIoTToCloudResult(aziotResult);
json_free_serialized_string(serializedTelemetry);
json_value_free(telemetryValue);
return result;
}
Cloud_Result Cloud_SendThermometerMovedEvent(void)
{
JSON_Value *thermometerMovedValue = json_value_init_object();
JSON_Object *thermometerMovedRoot = json_value_get_object(thermometerMovedValue);
json_object_dotset_boolean(thermometerMovedRoot, "thermometerMoved", 1);
char *serializedDeviceMoved = json_serialize_to_string(thermometerMovedValue);
AzureIoT_Result aziotResult = AzureIoT_SendTelemetry(serializedDeviceMoved, NULL);
Cloud_Result result = AzureIoTToCloudResult(aziotResult);
json_free_serialized_string(serializedDeviceMoved);
json_value_free(thermometerMovedValue);
return result;
}
Cloud_Result Cloud_SendThermometerTelemetryUploadEnabledChangedEvent(bool uploadEnabled)
{
JSON_Value *thermometerTelemetryUploadValue = json_value_init_object();
JSON_Object *thermometerTelemetryUploadRoot =
json_value_get_object(thermometerTelemetryUploadValue);
json_object_dotset_boolean(thermometerTelemetryUploadRoot,
"thermometerTelemetryUploadEnabled.value", uploadEnabled ? 1 : 0);
json_object_dotset_number(thermometerTelemetryUploadRoot,
"thermometerTelemetryUploadEnabled.ac", 200);
json_object_dotset_number(thermometerTelemetryUploadRoot,
"thermometerTelemetryUploadEnabled.av", latestVersion++);
json_object_dotset_string(thermometerTelemetryUploadRoot,
"thermometerTelemetryUploadEnabled.ad",
"Successfully updated thermometerTelemetryUploadEnabled");
char *serializedTelemetryUpload = json_serialize_to_string(thermometerTelemetryUploadValue);
AzureIoT_Result aziotResult = AzureIoT_DeviceTwinReportState(serializedTelemetryUpload, NULL);
Cloud_Result result = AzureIoTToCloudResult(aziotResult);
json_free_serialized_string(serializedTelemetryUpload);
json_value_free(thermometerTelemetryUploadValue);
return result;
}
Cloud_Result Cloud_SendDeviceDetails(const char *serialNumber)
{
// Send static device twin properties when connection is established.
JSON_Value *deviceDetailsValue = json_value_init_object();
JSON_Object *deviceDetailsRoot = json_value_get_object(deviceDetailsValue);
json_object_dotset_string(deviceDetailsRoot, "serialNumber", serialNumber);
char *serializedDeviceDetails = json_serialize_to_string(deviceDetailsValue);
AzureIoT_Result aziotResult = AzureIoT_DeviceTwinReportState(serializedDeviceDetails, NULL);
Cloud_Result result = AzureIoTToCloudResult(aziotResult);
json_free_serialized_string(serializedDeviceDetails);
json_value_free(deviceDetailsValue);
return result;
}
static void DefaultTelemetryUploadEnabledChangedHandler(bool uploadEnabled)
{
Log_Debug("WARNING: Cloud - no handler registered for TelemetryUploadEnabled - status %s\n",
uploadEnabled ? "true" : "false");
}
static void DefaultDisplayAlertHandler(const char *alertMessage)
{
Log_Debug("WARNING: Cloud - no handler registered for DisplayAlert - message %s\n",
alertMessage);
}
static void DefaultConnectionChangedHandler(bool connected)
{
Log_Debug("WARNING: Cloud - no handler registered for ConnectionChanged - status %s\n",
connected ? "true" : "false");
}
static void ConnectionChangedCallbackHandler(bool connected)
{
connectionChangedCallbackFunction(connected);
}
static void DeviceTwinCallbackHandler(const char *nullTerminatedJsonString)
{
JSON_Value *rootProperties = NULL;
rootProperties = json_parse_string(nullTerminatedJsonString);
if (rootProperties == NULL) {
Log_Debug("WARNING: Cannot parse the string as JSON content.\n");
goto cleanup;
}
JSON_Object *rootObject = json_value_get_object(rootProperties);
JSON_Object *desiredProperties = json_object_dotget_object(rootObject, "desired");
if (desiredProperties == NULL) {
desiredProperties = rootObject;
}
// The desired properties should have a "TelemetryUploadEnabled" object
int thermometerTelemetryUploadEnabledValue =
json_object_dotget_boolean(desiredProperties, "thermometerTelemetryUploadEnabled");
if (thermometerTelemetryUploadEnabledValue != -1) {
unsigned int requestedVersion =
(unsigned int)json_object_dotget_number(desiredProperties, "$version");
if (requestedVersion > latestVersion) {
latestVersion = requestedVersion;
}
thermometerTelemetryUploadEnabledChangedCallbackFunction(
thermometerTelemetryUploadEnabledValue == 1);
}
cleanup:
// Release the allocated memory.
json_value_free(rootProperties);
}
static int DeviceMethodCallbackHandler(const char *methodName, const unsigned char *payload,
size_t payloadSize, unsigned char **response,
size_t *responseSize)
{
int result;
char *responseString;
static char nullTerminatedPayload[MAX_PAYLOAD_SIZE + 1];
size_t actualPayloadSize = payloadSize > MAX_PAYLOAD_SIZE ? MAX_PAYLOAD_SIZE : payloadSize;
strncpy(nullTerminatedPayload, payload, actualPayloadSize);
nullTerminatedPayload[actualPayloadSize] = '\0';
if (strcmp("displayAlert", methodName) == 0) {
displayAlertCallbackFunction(nullTerminatedPayload);
responseString =
"\"Alert message displayed successfully.\""; // must be a JSON string (in quotes)
result = 200;
} else {
// All other method names are ignored
responseString = "{}";
result = -1;
}
// if 'response' is non-NULL, the Azure IoT library frees it after use, so copy it to heap
*responseSize = strlen(responseString);
*response = malloc(*responseSize);
memcpy(*response, responseString, *responseSize);
return result;
}