From f789318be7275f5347861926fa1272441a96e227 Mon Sep 17 00:00:00 2001 From: Tatiana Leon Date: Thu, 11 May 2017 18:25:42 +0200 Subject: [PATCH] aws iot sample: add support to remotely control an LED The application allows to switch on and off the configured LED. This commit adds the following configuration parameter: * 'user_led ': GPIO number of the LED to be remotely controlled. If -1, no LED is controlled, but the application simulates its status. https://jira.digi.com/browse/DEL-4149 Signed-off-by: Tatiana Leon --- awsiot-sample/README.md | 28 +++- awsiot-sample/cfg_files/awsiotsdk.conf | 4 + awsiot-sample/src/aws_config.c | 4 + awsiot-sample/src/aws_config.h | 2 + awsiot-sample/src/aws_control.c | 54 ++++++- awsiot-sample/src/aws_control.h | 10 ++ awsiot-sample/src/device_control.c | 205 +++++++++++++++++++++++++ awsiot-sample/src/device_control.h | 9 ++ awsiot-sample/src/main.c | 37 ++++- 9 files changed, 347 insertions(+), 6 deletions(-) diff --git a/awsiot-sample/README.md b/awsiot-sample/README.md index 3e1c0df..aa5240f 100644 --- a/awsiot-sample/README.md +++ b/awsiot-sample/README.md @@ -2,7 +2,8 @@ AWS IoT device SDK Demo Application =================================== Demo application to connect devices to AWS IoT. -This application monitors the CPU temperature and load. +This application monitors the CPU temperature and load and allows to remotely +switch on/off a device LED. The demo uploads the device Thing Shadow every minute or when the difference between the current value of temperature or load and the last reported is bigger @@ -51,12 +52,35 @@ You can specify the configuration file with `-c`: "state" : { "reported" : { "temperature" : 45.971, - "cpuLoad" : 4.504505 + "cpuLoad" : 4.504505, + "ledON" : false } }, "clientToken" : "-3" } ``` +* To switch on the configured LED send to the topic + `$aws/things//shadow/update`: + ``` + { + "state" : { + "desired" : { + "ledON" : true + } + } + } + ``` +* To switch off the configured LED send to the topic + `$aws/things//shadow/update`: + ``` + { + "state" : { + "desired" : { + "ledON" : false + } + } + } + ``` Compiling the application ------------------------- diff --git a/awsiot-sample/cfg_files/awsiotsdk.conf b/awsiot-sample/cfg_files/awsiotsdk.conf index 0a5262e..91a30d5 100644 --- a/awsiot-sample/cfg_files/awsiotsdk.conf +++ b/awsiot-sample/cfg_files/awsiotsdk.conf @@ -65,3 +65,7 @@ temperature_variation = 1 # By default, 10%. cpu_load_variation = 10 +# User LED: GPIO number of the LED. +# If -1, no LED is controlled, but the application still simulates its status. +# User LED on board are: ccimx6sbc, 34; ccimx6ulsbc, 488; ccimx6ulstarter, 75. +user_led = -1 diff --git a/awsiot-sample/src/aws_config.c b/awsiot-sample/src/aws_config.c index 2753f5d..8f4b8fa 100644 --- a/awsiot-sample/src/aws_config.c +++ b/awsiot-sample/src/aws_config.c @@ -47,6 +47,7 @@ #define SETTING_CPULOAD_VARIATION "cpu_load_variation" #define SETTING_CPULOAD_VARIATION_MIN 1 /* % */ #define SETTING_CPULOAD_VARIATION_MAX 50.0 /* % */ +#define SETTING_USER_LED "user_led" #define SETTING_UNKNOWN "__unknown" @@ -112,6 +113,8 @@ int parse_configuration(const char *const filename, aws_iot_cfg_t *aws_cfg) CFG_FLOAT (SETTING_TEMP_VARIATION, 1, CFGF_NONE), CFG_FLOAT (SETTING_CPULOAD_VARIATION, 10, CFGF_NONE), + CFG_INT (SETTING_USER_LED, -1, CFGF_NONE), + /* Needed for unknown settings. */ CFG_STR (SETTING_UNKNOWN, NULL, CFGF_NONE), CFG_END() @@ -199,6 +202,7 @@ static int fill_aws_iot_config(aws_iot_cfg_t *aws_cfg) aws_cfg->shadow_report_rate = cfg_getint(cfg, SETTING_SHADOW_REPORT_RATE); aws_cfg->temp_variation = cfg_getfloat(cfg, SETTING_TEMP_VARIATION); aws_cfg->cpuload_variation = cfg_getfloat(cfg, SETTING_CPULOAD_VARIATION); + aws_cfg->led_gpio = cfg_getint(cfg, SETTING_USER_LED); return 0; } diff --git a/awsiot-sample/src/aws_config.h b/awsiot-sample/src/aws_config.h index 2acc083..45cf9ff 100644 --- a/awsiot-sample/src/aws_config.h +++ b/awsiot-sample/src/aws_config.h @@ -49,6 +49,7 @@ * (C) to report again * @cpuload_variation: CPU load variation between last reported and current (%) * to report again + * @led_gpio: GPIO number of the LED */ typedef struct { char *thing_name; @@ -62,6 +63,7 @@ typedef struct { uint32_t shadow_report_rate; uint16_t temp_variation; uint16_t cpuload_variation; + int led_gpio; } aws_iot_cfg_t; /*------------------------------------------------------------------------------ diff --git a/awsiot-sample/src/aws_control.c b/awsiot-sample/src/aws_control.c index fd827c3..07ac02d 100644 --- a/awsiot-sample/src/aws_control.c +++ b/awsiot-sample/src/aws_control.c @@ -35,6 +35,8 @@ /*------------------------------------------------------------------------------ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ +static void led_actuate_callback(const char *json_string, uint32_t len, + jsonStruct_t *json_struct); static void shadow_update_status_callback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, @@ -175,6 +177,8 @@ IoT_Error_t disconnect_shadow_client(AWS_IoT_Client *mqtt_client) IoT_Error_t initialize_shadow(AWS_IoT_Client *mqtt_client, device_shadow_t *dev_shadow) { + IoT_Error_t rc = FAILURE; + device_shadow = dev_shadow; device_shadow->temp_handler.cb = NULL; @@ -193,9 +197,22 @@ IoT_Error_t initialize_shadow(AWS_IoT_Client *mqtt_client, device_shadow->cpu_load = 0; device_shadow->cpu_load_update = 0; + device_shadow->led_actuator.cb = led_actuate_callback; + device_shadow->led_actuator.pKey = ATTR_LED; + device_shadow->led_actuator.pData = &(device_shadow->led_on); + device_shadow->led_actuator.type = SHADOW_JSON_BOOL; + + device_shadow->led_on = false; + device_shadow->led_update = 0; + device_shadow->aws_config = &aws_cfg; - return SUCCESS; + rc = aws_iot_shadow_register_delta(mqtt_client, + &(device_shadow->led_actuator)); + if (rc != SUCCESS) + IOT_ERROR("Unable to register Shadow Delta for LED, error: %d", rc); + + return rc; } /** @@ -216,9 +233,10 @@ IoT_Error_t update_shadow(AWS_IoT_Client *mqtt_client) return rc; rc = aws_iot_shadow_add_reported(json_doc_buf, size_json_doc_buf, - 2, + 3, &(device_shadow->temp_handler), - &(device_shadow->cpu_load_handler)); + &(device_shadow->cpu_load_handler), + &(device_shadow->led_actuator)); if (rc != SUCCESS) return rc; @@ -232,6 +250,35 @@ IoT_Error_t update_shadow(AWS_IoT_Client *mqtt_client) (void *) device_shadow, 4, true); } +/** + * led_actuate_callback() - Callback to be executed on receiving the LED value + * + * @json_string: Thing name of the shadow to be updated. + * @len: The response of the action. + * @json_struct: The struct used to parse JSON value. + */ +static void led_actuate_callback(const char *json_string, uint32_t len, + jsonStruct_t *json_struct) +{ + bool status = false; + int led_gpio = aws_cfg.led_gpio; + IOT_UNUSED(json_string); + IOT_UNUSED(len); + + if (json_struct == NULL) + return; + + status = *(bool *) (json_struct->pData); + + if (led_gpio <= -1 || + (led_gpio > -1 && set_gpio_value(led_gpio, status) == 0)) { + IOT_INFO("Delta - LED state changed to %s", status ? ON : OFF); + device_shadow->led_update = 1; + } else { + IOT_ERROR("Error setting LED to %s", status ? ON : OFF); + } +} + /** * shadow_update_status_callback() - AWS IoT Device SDK update status callback * @@ -270,6 +317,7 @@ static void shadow_update_status_callback(const char *pThingName, IOT_INFO("Shadow update accepted"); dev_shadow->temp_update = 0; dev_shadow->cpu_load_update = 0; + dev_shadow->led_update = 0; break; } } diff --git a/awsiot-sample/src/aws_control.h b/awsiot-sample/src/aws_control.h index d3b5f2d..5d027a8 100644 --- a/awsiot-sample/src/aws_control.h +++ b/awsiot-sample/src/aws_control.h @@ -30,6 +30,10 @@ ------------------------------------------------------------------------------*/ #define ATTR_TEMPERATURE "temperature" #define ATTR_CPU_LOAD "cpuLoad" +#define ATTR_LED "ledON" + +#define ON "ON" +#define OFF "OFF" /*------------------------------------------------------------------------------ D A T A T Y P E S D E F I N I T I O N S @@ -44,6 +48,9 @@ * @cpu_load: Last CPU load reported (%) * @cpu_load_handler: CPU load handler * @cpu_load_update: CPU load value locally updated + * @led_on: Last LED value reported + * @led_actuator: LED actuator + * @led_update: LED value locally updated * @update_required: Update shadow immediately * @aws_config: AWS IoT Decive SDK configuration struct */ @@ -54,6 +61,9 @@ typedef struct { double cpu_load; jsonStruct_t cpu_load_handler; unsigned int cpu_load_update; + bool led_on; + jsonStruct_t led_actuator; + unsigned int led_update; unsigned int update_required; aws_iot_cfg_t *aws_config; } device_shadow_t; diff --git a/awsiot-sample/src/device_control.c b/awsiot-sample/src/device_control.c index 61e7e1b..7362cb1 100644 --- a/awsiot-sample/src/device_control.c +++ b/awsiot-sample/src/device_control.c @@ -18,6 +18,8 @@ */ #include +#include +#include #include #include @@ -31,10 +33,18 @@ #define FILE_CPU_TEMP "/sys/class/thermal/thermal_zone0/temp" #define FILE_CPU_LOAD "/proc/stat" +#define PATH_GPIO_FILES "/sys/class/gpio/" +#define EXPORT_FILE PATH_GPIO_FILES "export" +#define GPIO_DIR_FILE_FORMAT PATH_GPIO_FILES "gpio%d/direction" +#define GPIO_VALUE_FILE_FORMAT PATH_GPIO_FILES "gpio%d/value" + /*------------------------------------------------------------------------------ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ +static int export_gpio(int gpio); +static int is_gpio_exported( int gpio); static long read_file(const char * path, char *buffer, long bytes_to_read); +static int write_file(const char *const path, const char *const format, ...); /*------------------------------------------------------------------------------ F U N C T I O N D E F I N I T I O N S @@ -114,6 +124,158 @@ double get_cpu_temp(void) return temperature / 1000; } +/** + * init_gpio() - Export and initialize a GPIO + * + * @gpio: GPIO kernel number + * + * The provided GPIO is exported and configured as input with edge "none". + * + * Return: 0 if the GPIO was initialized successfully, -1 otherwise. + */ +int init_gpio(int gpio) +{ + int error = 0; + + if (gpio < 0) { + IOT_ERROR("Bad GPIO number '%d'", gpio); + return -1; + } + + /* Export the GPIO. */ + if (export_gpio(gpio) != 0) { + IOT_ERROR("Cannot initialize GPIO %d", gpio); + return -1; + } + + /* Configure GPIO as input. */ + error = set_gpio_direction(gpio, INPUT); + if (error != 0) + IOT_ERROR("Cannot initialize GPIO %d", gpio); + + return error; +} + +/** + * set_gpio_value() - Set GPIO value, if it is configured as an output + * + * @gpio: GPIO kernel number. + * @value: 1 to set to high, 0 to low. + * + * Return: 0 if the GPIO was successfully set, -1 otherwise. + */ +int set_gpio_value(int gpio, unsigned int value) +{ + char gpio_path[MAX_LENGTH] = {0}; + + if (gpio < 0) { + IOT_ERROR("Bad GPIO number '%d'", gpio); + return -1; + } + + snprintf(gpio_path, MAX_LENGTH, GPIO_VALUE_FILE_FORMAT, gpio); + + if (write_file(gpio_path, "%d", value) != 0) { + IOT_ERROR("Error setting GPIO %d value to %s (%d)", gpio, + value ? "high" : "low", value); + return -1; + } + + return 0; +} + +/** + * set_gpio_direction() - Set GPIO direction, input or output + * + * @gpio: GPIO kernel number. + * @out: 1 to configure as out, 0 as input. + * + * Return: 0 if the GPIO was successfully configured, -1 otherwise. + */ +int set_gpio_direction(int gpio, unsigned int out) +{ + char gpio_path[MAX_LENGTH] = {0}; + char buf[MAX_LENGTH] = {0}; + + IOT_DEBUG("GPIO %d as %s", gpio, out ? "Output" : "Input"); + + if (gpio < 0) { + IOT_ERROR("Bad GPIO number '%d'", gpio); + return -1; + } + + snprintf(gpio_path, MAX_LENGTH, GPIO_DIR_FILE_FORMAT, gpio); + + if (out) + strcpy(buf, "out"); + else + strcpy(buf, "in"); + + if (write_file(gpio_path, "%s", buf) != 0) { + IOT_ERROR("Error setting GPIO %d direction to '%s'", gpio, + out ? "Output" : "Input"); + return -1; + } + + return 0; +} + +/** + * export_gpio() - Export a GPIO + * + * @gpio: GPIO kernel number. + * + * Return: 0 if the GPIO was successfully exported, -1 otherwise. + */ +static int export_gpio(int gpio) +{ + if (gpio < 0) { + IOT_ERROR("Bad GPIO number '%d'", gpio); + return -1; + } + + if (is_gpio_exported(gpio)) + return 0; + + if (write_file(EXPORT_FILE, "%d", gpio) != 0) { + IOT_ERROR("GPIO %d not exported, error writing to 'export' file", + gpio); + return -1; + } + + /* Check if the GPIO was really exported. */ + if (!is_gpio_exported(gpio)) { + IOT_ERROR("GPIO %d not exported", gpio); + return -1; + } + + return 0; +} + +/** + * is_gpio_exported() - Check if a GPIO is exported + * + * @gpio: GPIO kernel number. + * + * Return: 1 if the GPIO is exported, 0 otherwise. + */ +static int is_gpio_exported( int gpio) +{ + char dir_path[MAX_LENGTH] = {0}; + DIR *dir = NULL; + + snprintf(dir_path, MAX_LENGTH, PATH_GPIO_FILES "gpio%d", gpio); + + dir = opendir(dir_path); + + if (dir) { + closedir(dir); + return 1; + } else { + return 0; + } +} + /** * read_file() - Read the given file and returns its contents * @@ -150,3 +312,46 @@ static long read_file(const char * path, char *buffer, long bytes_to_read) return read_size; } + +/** + * write_file() - Write data to a file + * + * @path: Absolute path of the file to be written. + * @format: String that contains the text to be written to the file. + * + * Return: 0 if the file was written successfully, -1 otherwise. + */ +static int write_file(const char *const path, const char *const format, ...) +{ + va_list args; + FILE *f = NULL; + int len, error = 0; + + if (access(path, W_OK) != 0) { + IOT_DEBUG("File cannot be written: %s", path); + return -1; + } + + va_start(args, format); + + f = fopen(path, "w"); + if (f == NULL) { + IOT_DEBUG("fopen error: %s", path); + error = -1; + goto done; + } + + len = vfprintf(f, format, args); + if (len < 0) { + IOT_DEBUG("vfprintf error: %s", path); + error = -1; + } + + fsync(fileno(f)); + fclose(f); + +done: + va_end(args); + + return error; +} diff --git a/awsiot-sample/src/device_control.h b/awsiot-sample/src/device_control.h index 5556d2b..b112980 100644 --- a/awsiot-sample/src/device_control.h +++ b/awsiot-sample/src/device_control.h @@ -20,10 +20,19 @@ #ifndef DEVICE_CONTROL_H_ #define DEVICE_CONTROL_H_ +/*------------------------------------------------------------------------------ + D E F I N I T I O N S +------------------------------------------------------------------------------*/ +#define INPUT 0 +#define OUTPUT 1 + /*------------------------------------------------------------------------------ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ double get_cpu_load(void); double get_cpu_temp(void); +int init_gpio(int gpio); +int set_gpio_value(int gpio, unsigned int value); +int set_gpio_direction(int gpio, unsigned int out); #endif /* DEVICE_CONTROL_H_ */ diff --git a/awsiot-sample/src/main.c b/awsiot-sample/src/main.c index ca2a98e..e07d734 100644 --- a/awsiot-sample/src/main.c +++ b/awsiot-sample/src/main.c @@ -58,6 +58,7 @@ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ static int start_aws_iot(const char *config_file); +static int initialize_system(device_shadow_t *dev_shadow); static int check_stop(void); static void add_sigkill_signal(void); static void graceful_shutdown(void); @@ -160,6 +161,9 @@ static int start_aws_iot(const char *config_file) goto done; } + if (initialize_system(&device_shadow) != 0) + IOT_WARN("Unable to initialize the whole system"); + time_start = time(NULL); /* Loop and publish shadow changes */ while (rc == NETWORK_ATTEMPTING_RECONNECT || @@ -189,7 +193,8 @@ static int start_aws_iot(const char *config_file) load >= (device_shadow.cpu_load + aws_cfg->cpuload_variation)); change_shadow = device_shadow.temp_update || - device_shadow.cpu_load_update; + device_shadow.cpu_load_update || + device_shadow.led_update; if (change_shadow || (time(NULL) - time_start) >= aws_cfg->shadow_report_rate) { @@ -206,8 +211,15 @@ static int start_aws_iot(const char *config_file) IOT_INFO( "CPU Load variation greater than %d%%\n", aws_cfg->cpuload_variation); + if (device_shadow.led_update) + IOT_INFO( + "LED changed to %s\n", + device_shadow.led_on ? ON : OFF); IOT_INFO("Temperature: %fC", t); IOT_INFO("CPU Load: %f%%", load); + IOT_INFO( + "LED status: %s", + device_shadow.led_on ? ON : OFF); IOT_INFO("=========================================\n"); rc = update_shadow(&mqtt_client); @@ -228,6 +240,29 @@ done: return rc; } +/* + * initialize_system() - Stop application + * + * @dev_shadow: Device shadow. + * + * Return: 0 if it is successfully initialized, -1 otherwise. + */ +static int initialize_system(device_shadow_t *dev_shadow) +{ + int led_gpio = dev_shadow->aws_config->led_gpio; + + if (led_gpio > -1 && + (init_gpio(led_gpio) != 0 || + set_gpio_direction(led_gpio, OUTPUT) != 0 || + set_gpio_value(led_gpio, dev_shadow->led_on) != 0)) { + IOT_ERROR("Unable to initialize User LED"); + dev_shadow->aws_config->led_gpio = -1; + return -1; + } + + return 0; +} + /* * check_stop() - Stop application *