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 <tatiana.leon@digi.com>
This commit is contained in:
Tatiana Leon 2017-05-11 18:25:42 +02:00
parent 4837f8edc5
commit f789318be7
9 changed files with 347 additions and 6 deletions

View File

@ -2,7 +2,8 @@ AWS IoT device SDK Demo Application
=================================== ===================================
Demo application to connect devices to AWS IoT. 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 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 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" : { "state" : {
"reported" : { "reported" : {
"temperature" : 45.971, "temperature" : 45.971,
"cpuLoad" : 4.504505 "cpuLoad" : 4.504505,
"ledON" : false
} }
}, },
"clientToken" : "<thing_name>-3" "clientToken" : "<thing_name>-3"
} }
``` ```
* To switch on the configured LED send to the topic
`$aws/things/<thing_name>/shadow/update`:
```
{
"state" : {
"desired" : {
"ledON" : true
}
}
}
```
* To switch off the configured LED send to the topic
`$aws/things/<thing_name>/shadow/update`:
```
{
"state" : {
"desired" : {
"ledON" : false
}
}
}
```
Compiling the application Compiling the application
------------------------- -------------------------

View File

@ -65,3 +65,7 @@ temperature_variation = 1
# By default, 10%. # By default, 10%.
cpu_load_variation = 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

View File

@ -47,6 +47,7 @@
#define SETTING_CPULOAD_VARIATION "cpu_load_variation" #define SETTING_CPULOAD_VARIATION "cpu_load_variation"
#define SETTING_CPULOAD_VARIATION_MIN 1 /* % */ #define SETTING_CPULOAD_VARIATION_MIN 1 /* % */
#define SETTING_CPULOAD_VARIATION_MAX 50.0 /* % */ #define SETTING_CPULOAD_VARIATION_MAX 50.0 /* % */
#define SETTING_USER_LED "user_led"
#define SETTING_UNKNOWN "__unknown" #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_TEMP_VARIATION, 1, CFGF_NONE),
CFG_FLOAT (SETTING_CPULOAD_VARIATION, 10, CFGF_NONE), CFG_FLOAT (SETTING_CPULOAD_VARIATION, 10, CFGF_NONE),
CFG_INT (SETTING_USER_LED, -1, CFGF_NONE),
/* Needed for unknown settings. */ /* Needed for unknown settings. */
CFG_STR (SETTING_UNKNOWN, NULL, CFGF_NONE), CFG_STR (SETTING_UNKNOWN, NULL, CFGF_NONE),
CFG_END() 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->shadow_report_rate = cfg_getint(cfg, SETTING_SHADOW_REPORT_RATE);
aws_cfg->temp_variation = cfg_getfloat(cfg, SETTING_TEMP_VARIATION); aws_cfg->temp_variation = cfg_getfloat(cfg, SETTING_TEMP_VARIATION);
aws_cfg->cpuload_variation = cfg_getfloat(cfg, SETTING_CPULOAD_VARIATION); aws_cfg->cpuload_variation = cfg_getfloat(cfg, SETTING_CPULOAD_VARIATION);
aws_cfg->led_gpio = cfg_getint(cfg, SETTING_USER_LED);
return 0; return 0;
} }

View File

@ -49,6 +49,7 @@
* (C) to report again * (C) to report again
* @cpuload_variation: CPU load variation between last reported and current (%) * @cpuload_variation: CPU load variation between last reported and current (%)
* to report again * to report again
* @led_gpio: GPIO number of the LED
*/ */
typedef struct { typedef struct {
char *thing_name; char *thing_name;
@ -62,6 +63,7 @@ typedef struct {
uint32_t shadow_report_rate; uint32_t shadow_report_rate;
uint16_t temp_variation; uint16_t temp_variation;
uint16_t cpuload_variation; uint16_t cpuload_variation;
int led_gpio;
} aws_iot_cfg_t; } aws_iot_cfg_t;
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------

View File

@ -35,6 +35,8 @@
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
F U N C T I O N D E C L A R A T I O N S 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, static void shadow_update_status_callback(const char *pThingName,
ShadowActions_t action, ShadowActions_t action,
Shadow_Ack_Status_t status, 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, IoT_Error_t initialize_shadow(AWS_IoT_Client *mqtt_client,
device_shadow_t *dev_shadow) device_shadow_t *dev_shadow)
{ {
IoT_Error_t rc = FAILURE;
device_shadow = dev_shadow; device_shadow = dev_shadow;
device_shadow->temp_handler.cb = NULL; 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 = 0;
device_shadow->cpu_load_update = 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; 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; return rc;
rc = aws_iot_shadow_add_reported(json_doc_buf, size_json_doc_buf, rc = aws_iot_shadow_add_reported(json_doc_buf, size_json_doc_buf,
2, 3,
&(device_shadow->temp_handler), &(device_shadow->temp_handler),
&(device_shadow->cpu_load_handler)); &(device_shadow->cpu_load_handler),
&(device_shadow->led_actuator));
if (rc != SUCCESS) if (rc != SUCCESS)
return rc; return rc;
@ -232,6 +250,35 @@ IoT_Error_t update_shadow(AWS_IoT_Client *mqtt_client)
(void *) device_shadow, 4, true); (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 * 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"); IOT_INFO("Shadow update accepted");
dev_shadow->temp_update = 0; dev_shadow->temp_update = 0;
dev_shadow->cpu_load_update = 0; dev_shadow->cpu_load_update = 0;
dev_shadow->led_update = 0;
break; break;
} }
} }

View File

@ -30,6 +30,10 @@
------------------------------------------------------------------------------*/ ------------------------------------------------------------------------------*/
#define ATTR_TEMPERATURE "temperature" #define ATTR_TEMPERATURE "temperature"
#define ATTR_CPU_LOAD "cpuLoad" #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 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: Last CPU load reported (%)
* @cpu_load_handler: CPU load handler * @cpu_load_handler: CPU load handler
* @cpu_load_update: CPU load value locally updated * @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 * @update_required: Update shadow immediately
* @aws_config: AWS IoT Decive SDK configuration struct * @aws_config: AWS IoT Decive SDK configuration struct
*/ */
@ -54,6 +61,9 @@ typedef struct {
double cpu_load; double cpu_load;
jsonStruct_t cpu_load_handler; jsonStruct_t cpu_load_handler;
unsigned int cpu_load_update; unsigned int cpu_load_update;
bool led_on;
jsonStruct_t led_actuator;
unsigned int led_update;
unsigned int update_required; unsigned int update_required;
aws_iot_cfg_t *aws_config; aws_iot_cfg_t *aws_config;
} device_shadow_t; } device_shadow_t;

View File

@ -18,6 +18,8 @@
*/ */
#include <aws_iot_log.h> #include <aws_iot_log.h>
#include <dirent.h>
#include <stdarg.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@ -31,10 +33,18 @@
#define FILE_CPU_TEMP "/sys/class/thermal/thermal_zone0/temp" #define FILE_CPU_TEMP "/sys/class/thermal/thermal_zone0/temp"
#define FILE_CPU_LOAD "/proc/stat" #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 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 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 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; 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 * 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; 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;
}

View File

@ -20,10 +20,19 @@
#ifndef DEVICE_CONTROL_H_ #ifndef DEVICE_CONTROL_H_
#define 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 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_load(void);
double get_cpu_temp(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_ */ #endif /* DEVICE_CONTROL_H_ */

View File

@ -58,6 +58,7 @@
F U N C T I O N D E C L A R A T I O N S 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 start_aws_iot(const char *config_file);
static int initialize_system(device_shadow_t *dev_shadow);
static int check_stop(void); static int check_stop(void);
static void add_sigkill_signal(void); static void add_sigkill_signal(void);
static void graceful_shutdown(void); static void graceful_shutdown(void);
@ -160,6 +161,9 @@ static int start_aws_iot(const char *config_file)
goto done; goto done;
} }
if (initialize_system(&device_shadow) != 0)
IOT_WARN("Unable to initialize the whole system");
time_start = time(NULL); time_start = time(NULL);
/* Loop and publish shadow changes */ /* Loop and publish shadow changes */
while (rc == NETWORK_ATTEMPTING_RECONNECT || 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)); load >= (device_shadow.cpu_load + aws_cfg->cpuload_variation));
change_shadow = device_shadow.temp_update || change_shadow = device_shadow.temp_update ||
device_shadow.cpu_load_update; device_shadow.cpu_load_update ||
device_shadow.led_update;
if (change_shadow || if (change_shadow ||
(time(NULL) - time_start) >= aws_cfg->shadow_report_rate) { (time(NULL) - time_start) >= aws_cfg->shadow_report_rate) {
@ -206,8 +211,15 @@ static int start_aws_iot(const char *config_file)
IOT_INFO( IOT_INFO(
"CPU Load variation greater than %d%%\n", "CPU Load variation greater than %d%%\n",
aws_cfg->cpuload_variation); 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("Temperature: %fC", t);
IOT_INFO("CPU Load: %f%%", load); IOT_INFO("CPU Load: %f%%", load);
IOT_INFO(
"LED status: %s",
device_shadow.led_on ? ON : OFF);
IOT_INFO("=========================================\n"); IOT_INFO("=========================================\n");
rc = update_shadow(&mqtt_client); rc = update_shadow(&mqtt_client);
@ -228,6 +240,29 @@ done:
return rc; 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 * check_stop() - Stop application
* *