/* * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. * Additions Copyright 2016 Espressif Systems (Shanghai) PTE LTD * * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /** * @file thing_shadow_sample.c * @brief A simple connected window example demonstrating the use of Thing Shadow * * See example README for more details. */ #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event_loop.h" #include "esp_log.h" #include "esp_vfs_fat.h" #include "driver/sdmmc_host.h" #include "nvs.h" #include "nvs_flash.h" #include "aws_iot_config.h" #include "aws_iot_log.h" #include "aws_iot_version.h" #include "aws_iot_mqtt_client_interface.h" #include "aws_iot_shadow_interface.h" /*! * The goal of this sample application is to demonstrate the capabilities of shadow. * This device(say Connected Window) will open the window of a room based on temperature * It can report to the Shadow the following parameters: * 1. temperature of the room (double) * 2. status of the window (open or close) * It can act on commands from the cloud. In this case it will open or close the window based on the json object "windowOpen" data[open/close] * * The two variables from a device's perspective are double temperature and bool windowOpen * The device needs to act on only on windowOpen variable, so we will create a primitiveJson_t object with callback The Json Document in the cloud will be { "reported": { "temperature": 0, "windowOpen": false }, "desired": { "windowOpen": false } } */ static const char *TAG = "shadow"; #define ROOMTEMPERATURE_UPPERLIMIT 32.0f #define ROOMTEMPERATURE_LOWERLIMIT 25.0f #define STARTING_ROOMTEMPERATURE ROOMTEMPERATURE_LOWERLIMIT #define MAX_LENGTH_OF_UPDATE_JSON_BUFFER 200 /* The examples use simple WiFi configuration that you can set via 'make menuconfig'. If you'd rather not, just change the below entries to strings with the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" */ #define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID #define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD /* FreeRTOS event group to signal when we are connected & ready to make a request */ static EventGroupHandle_t wifi_event_group; /* The event group allows multiple bits for each event, but we only care about one event - are we connected to the AP with an IP? */ const int CONNECTED_BIT = BIT0; /* CA Root certificate, device ("Thing") certificate and device * ("Thing") key. Example can be configured one of two ways: "Embedded Certs" are loaded from files in "certs/" and embedded into the app binary. "Filesystem Certs" are loaded from the filesystem (SD card, etc.) See example README for more details. */ #if defined(CONFIG_EXAMPLE_EMBEDDED_CERTS) extern const uint8_t aws_root_ca_pem_start[] asm("_binary_aws_root_ca_pem_start"); extern const uint8_t aws_root_ca_pem_end[] asm("_binary_aws_root_ca_pem_end"); extern const uint8_t certificate_pem_crt_start[] asm("_binary_certificate_pem_crt_start"); extern const uint8_t certificate_pem_crt_end[] asm("_binary_certificate_pem_crt_end"); extern const uint8_t private_pem_key_start[] asm("_binary_private_pem_key_start"); extern const uint8_t private_pem_key_end[] asm("_binary_private_pem_key_end"); #elif defined(CONFIG_EXAMPLE_FILESYSTEM_CERTS) static const char * DEVICE_CERTIFICATE_PATH = CONFIG_EXAMPLE_CERTIFICATE_PATH; static const char * DEVICE_PRIVATE_KEY_PATH = CONFIG_EXAMPLE_PRIVATE_KEY_PATH; static const char * ROOT_CA_PATH = CONFIG_EXAMPLE_ROOT_CA_PATH; #else #error "Invalid method for loading certs" #endif static esp_err_t event_handler(void *ctx, system_event_t *event) { switch(event->event_id) { case SYSTEM_EVENT_STA_START: esp_wifi_connect(); break; case SYSTEM_EVENT_STA_GOT_IP: xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); break; case SYSTEM_EVENT_STA_DISCONNECTED: /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ esp_wifi_connect(); xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); break; default: break; } return ESP_OK; } static void simulateRoomTemperature(float *pRoomTemperature) { static float deltaChange; if(*pRoomTemperature >= ROOMTEMPERATURE_UPPERLIMIT) { deltaChange = -0.5f; } else if(*pRoomTemperature <= ROOMTEMPERATURE_LOWERLIMIT) { deltaChange = 0.5f; } *pRoomTemperature += deltaChange; } static bool shadowUpdateInProgress; void ShadowUpdateStatusCallback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, const char *pReceivedJsonDocument, void *pContextData) { IOT_UNUSED(pThingName); IOT_UNUSED(action); IOT_UNUSED(pReceivedJsonDocument); IOT_UNUSED(pContextData); shadowUpdateInProgress = false; if(SHADOW_ACK_TIMEOUT == status) { ESP_LOGE(TAG, "Update timed out"); } else if(SHADOW_ACK_REJECTED == status) { ESP_LOGE(TAG, "Update rejected"); } else if(SHADOW_ACK_ACCEPTED == status) { ESP_LOGI(TAG, "Update accepted"); } } void windowActuate_Callback(const char *pJsonString, uint32_t JsonStringDataLen, jsonStruct_t *pContext) { IOT_UNUSED(pJsonString); IOT_UNUSED(JsonStringDataLen); if(pContext != NULL) { ESP_LOGI(TAG, "Delta - Window state changed to %d", *(bool *) (pContext->pData)); } } void aws_iot_task(void *param) { IoT_Error_t rc = FAILURE; char JsonDocumentBuffer[MAX_LENGTH_OF_UPDATE_JSON_BUFFER]; size_t sizeOfJsonDocumentBuffer = sizeof(JsonDocumentBuffer) / sizeof(JsonDocumentBuffer[0]); float temperature = 0.0; bool windowOpen = false; jsonStruct_t windowActuator; windowActuator.cb = windowActuate_Callback; windowActuator.pData = &windowOpen; windowActuator.pKey = "windowOpen"; windowActuator.type = SHADOW_JSON_BOOL; windowActuator.dataLength = sizeof(bool); jsonStruct_t temperatureHandler; temperatureHandler.cb = NULL; temperatureHandler.pKey = "temperature"; temperatureHandler.pData = &temperature; temperatureHandler.type = SHADOW_JSON_FLOAT; temperatureHandler.dataLength = sizeof(float); ESP_LOGI(TAG, "AWS IoT SDK Version %d.%d.%d-%s", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); // initialize the mqtt client AWS_IoT_Client mqttClient; ShadowInitParameters_t sp = ShadowInitParametersDefault; sp.pHost = AWS_IOT_MQTT_HOST; sp.port = AWS_IOT_MQTT_PORT; #if defined(CONFIG_EXAMPLE_EMBEDDED_CERTS) sp.pClientCRT = (const char *)certificate_pem_crt_start; sp.pClientKey = (const char *)private_pem_key_start; sp.pRootCA = (const char *)aws_root_ca_pem_start; #elif defined(CONFIG_EXAMPLE_FILESYSTEM_CERTS) sp.pClientCRT = DEVICE_CERTIFICATE_PATH; sp.pClientKey = DEVICE_PRIVATE_KEY_PATH; sp.pRootCA = ROOT_CA_PATH; #endif sp.enableAutoReconnect = false; sp.disconnectHandler = NULL; #ifdef CONFIG_EXAMPLE_SDCARD_CERTS ESP_LOGI(TAG, "Mounting SD card..."); sdmmc_host_t host = SDMMC_HOST_DEFAULT(); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 3, }; sdmmc_card_t* card; esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret)); abort(); } #endif /* Wait for WiFI to show as connected */ xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); ESP_LOGI(TAG, "Shadow Init"); rc = aws_iot_shadow_init(&mqttClient, &sp); if(SUCCESS != rc) { ESP_LOGE(TAG, "aws_iot_shadow_init returned error %d, aborting...", rc); abort(); } ShadowConnectParameters_t scp = ShadowConnectParametersDefault; scp.pMyThingName = CONFIG_AWS_EXAMPLE_THING_NAME; scp.pMqttClientId = CONFIG_AWS_EXAMPLE_CLIENT_ID; scp.mqttClientIdLen = (uint16_t) strlen(CONFIG_AWS_EXAMPLE_CLIENT_ID); ESP_LOGI(TAG, "Shadow Connect"); rc = aws_iot_shadow_connect(&mqttClient, &scp); if(SUCCESS != rc) { ESP_LOGE(TAG, "aws_iot_shadow_connect returned error %d, aborting...", rc); abort(); } /* * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL */ rc = aws_iot_shadow_set_autoreconnect_status(&mqttClient, true); if(SUCCESS != rc) { ESP_LOGE(TAG, "Unable to set Auto Reconnect to true - %d, aborting...", rc); abort(); } rc = aws_iot_shadow_register_delta(&mqttClient, &windowActuator); if(SUCCESS != rc) { ESP_LOGE(TAG, "Shadow Register Delta Error"); } temperature = STARTING_ROOMTEMPERATURE; // loop and publish a change in temperature while(NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) { rc = aws_iot_shadow_yield(&mqttClient, 200); if(NETWORK_ATTEMPTING_RECONNECT == rc || shadowUpdateInProgress) { rc = aws_iot_shadow_yield(&mqttClient, 1000); // If the client is attempting to reconnect, or already waiting on a shadow update, // we will skip the rest of the loop. continue; } ESP_LOGI(TAG, "======================================================================================="); ESP_LOGI(TAG, "On Device: window state %s", windowOpen ? "true" : "false"); simulateRoomTemperature(&temperature); rc = aws_iot_shadow_init_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer); if(SUCCESS == rc) { rc = aws_iot_shadow_add_reported(JsonDocumentBuffer, sizeOfJsonDocumentBuffer, 2, &temperatureHandler, &windowActuator); if(SUCCESS == rc) { rc = aws_iot_finalize_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer); if(SUCCESS == rc) { ESP_LOGI(TAG, "Update Shadow: %s", JsonDocumentBuffer); rc = aws_iot_shadow_update(&mqttClient, CONFIG_AWS_EXAMPLE_THING_NAME, JsonDocumentBuffer, ShadowUpdateStatusCallback, NULL, 4, true); shadowUpdateInProgress = true; } } } ESP_LOGI(TAG, "*****************************************************************************************"); ESP_LOGI(TAG, "Stack remaining for task '%s' is %d bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); vTaskDelay(1000 / portTICK_RATE_MS); } if(SUCCESS != rc) { ESP_LOGE(TAG, "An error occurred in the loop %d", rc); } ESP_LOGI(TAG, "Disconnecting"); rc = aws_iot_shadow_disconnect(&mqttClient); if(SUCCESS != rc) { ESP_LOGE(TAG, "Disconnect error %d", rc); } vTaskDelete(NULL); } static void initialise_wifi(void) { tcpip_adapter_init(); wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_WIFI_SSID, .password = EXAMPLE_WIFI_PASS, }, }; ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); ESP_ERROR_CHECK( esp_wifi_start() ); } void app_main() { esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK( err ); initialise_wifi(); /* Temporarily pin task to core, due to FPU uncertainty */ xTaskCreatePinnedToCore(&aws_iot_task, "aws_iot_task", 9216, NULL, 5, NULL, 1); }