cloud/simplemq/main.c

See examples/cloud/simplemq

/*
* ZentriOS SDK LICENSE AGREEMENT | Zentri.com, 2015.
*
* Use of source code and/or libraries contained in the ZentriOS SDK is
* subject to the Zentri Operating System SDK license agreement and
* applicable open source license agreements.
*
*/
#include "zos.h"
#include <SMQClient.h>
#include "ledctrl.h"
/* Change the domain/url if you are running your own broker */
#define SIMPLEMQ_DOMAIN "http://simplemq.com"
#define SIMPLEMQ_URL SIMPLEMQ_DOMAIN "/smq.lsp"
#define CONNECT_RETRY_TIMEOUT 5000 // ms
#define ADD_LED(_color, _id, _name, _led_gpio, _btn_gpio) [_id] = \
{ \
.color = LedColor_ ## _color, \
.id = _id, \
.name = _name, \
.platform_led_gpio = _led_gpio, \
.platform_btn_gpio = _btn_gpio \
}
static const LedInfo const led_info[] =
{
ADD_LED(green, 0, "LED 1", PLATFORM_LED1, PLATFORM_BUTTON1),
ADD_LED(green, 1, "LED 2", PLATFORM_LED2, PLATFORM_BUTTON2),
#ifdef PLATFORM_LED3
ADD_LED(green, 2, "LED 3", PLATFORM_LED3, PLATFORM_BUTTON3),
#endif
#ifdef PLATFORM_LED4
ADD_LED(blue, 3, "LED 4", PLATFORM_LED4, PLATFORM_BUTTON4),
#endif
};
static struct
{
struct
{
uint32_t display;
uint32_t led_sub;
uint32_t device;
uint32_t dev_info_sub;
} tids;
SMQ *msg;
uint8_t msg_buf[127];
char ipaddr[16];
zos_bool_t is_running;
} smq_context;
/*************************************************************************************************/
void zn_app_init(void)
{
zos_result_t result;
smq_context.msg = ZOS_CREATE_OBJECT(SMQ);
if(smq_context.msg == NULL)
{
ZOS_LOG("Failed to allocate memory of SMQ object");
return;
}
SMQ_constructor(smq_context.msg, smq_context.msg_buf, sizeof(smq_context.msg_buf));
{
ZOS_LOG("Failed to start network: %d", result);
}
else
{
ZOS_LOG("SimpleMQ Demo Ready.");
zn_event_issue(connect_to_broker_event_handler, NULL, 0);
smq_context.is_running = ZOS_TRUE;
const button_config_t btn_config =
{
.active_level = BUTTON_ACTIVE_HIGH,
.debounce = 75,
.event_handler.toggle = button_event_handler
};
for(int i = 0; i < ARRAY_COUNT(led_info); ++i)
{
zn_gpio_init(led_info[i].platform_led_gpio, GPIO_OUTPUT_PUSHPULL, ZOS_FALSE);
button_init(led_info[i].platform_btn_gpio, &btn_config, (void*)i);
}
}
}
/*************************************************************************************************/
void zn_app_deinit(void)
{
ZOS_LOG("SimpleMQ demo de-initializing");
zn_event_unregister(connect_to_broker_event_handler, NULL);
for(int i = 0; i < ARRAY_COUNT(led_info); ++i)
{
button_deinit(led_info[i].platform_btn_gpio);
}
SMQ_destructor(smq_context.msg);
ZOS_DESTROY_OBJECT(smq_context.msg);
}
/*************************************************************************************************/
zos_bool_t zn_app_idle(void)
{
return smq_context.is_running;
}
/*************************************************************************************************
* This event handler is called when we're trying to connected to the broken
*/
static void connect_to_broker_event_handler(void *arg)
{
if(!smq_context.is_running)
{
return;
}
char device_uuid[DEVICE_UUID_LEN+1];
char device_info[100];
// retrieve the module's MAC address
zn_settings_get_str("sy u", device_uuid, sizeof(device_uuid));
get_device_name(device_info);
ZOS_LOG("Connecting to %s", SIMPLEMQ_URL);
if(SMQ_init(smq_context.msg, SIMPLEMQ_URL, NULL) != 0)
{
ZOS_LOG("Cannot establish connection, status: %d", smq_context.msg->status);
// failed to connect, try again in after timeout
zn_event_register_timed(connect_to_broker_event_handler, NULL, CONNECT_RETRY_TIMEOUT, 0);
return;
}
/* Fetch the IP address sent by the broker. We use this for the
* text shown in the left pane tab in the browser's user interface.
*/
strncpy(smq_context.ipaddr, (char*)smq_context.msg->buf, 16);
smq_context.ipaddr[15]=0;
if(SMQ_connect(smq_context.msg,
device_uuid, DEVICE_UUID_LEN,
NULL, 0, /* credentials */
device_info, strlen(device_info)) != 0)
{
ZOS_LOG("Connect failed, status: %d\n", smq_context.msg->status);
// failed to connect, try again in after timeout
zn_event_register_timed(connect_to_broker_event_handler, NULL, CONNECT_RETRY_TIMEOUT, 0);
return;
}
// set the read timeout to 0, ZentriOS apps are event driven
// this means we sleep while we're waiting for a message event
smq_context.msg->timeout = 0;
ZOS_LOG("\r\nConnected to %s\r\n"
"Use a browser and navigate to this domain.", SIMPLEMQ_DOMAIN);
/* Request broker to return a topic ID for "/m2m/led/device", the
* topic where we publish the device capabilities as JSON data.
*/
SMQ_create(smq_context.msg, "/m2m/led/device");
SMQ_createsub(smq_context.msg, "devinfo");
SMQ_createsub(smq_context.msg, "led");
/* Subscribe to browser "hello" messages. We send the device
* capabilities as JSON data to the browser's ephemeral ID when we
* receive a hello message from a browser.
*/
SMQ_subscribe(smq_context.msg, "/m2m/led/display");
/* Register a message event handler */
SMQ_register_message_handler(&smq_context.msg->sock, smq_message_handler);
}
/*************************************************************************************************
* The event handler is called when one or more messages are available to be read
*/
static void smq_message_handler(void *arg)
{
/* While there are messages to be read */
for(;;)
{
if(!smq_context.is_running)
{
return;
}
uint8_t* msg;
int msg_len = SMQ_getMessage(smq_context.msg, &msg);
if(msg_len < 0) /* We received a control message or an error code */
{
switch(msg_len)
{
/* Control messages */
/* Manage responses for create, createsub, and subscribe */
case SMQ_SUBACK: /* ACK: "/m2m/led/display" */
smq_context.tids.display = smq_context.msg->ptid;
break;
case SMQ_CREATEACK: /* ACK: "/m2m/led/device" */
smq_context.tids.device = smq_context.msg->ptid;
SMQ_observe(smq_context.msg, smq_context.tids.device);
break;
case SMQ_CREATESUBACK:
/* We get two suback messages ("devinfo" and "led") */
if( ! strcmp("led", (char*)msg ) )
{ /* Ack for: SMQ_createsub(smq, "led"); */
smq_context.tids.led_sub = smq_context.msg->ptid;
}
else /* Must be ACK for devinfo */
{ /* Ack for: SMQ_createsub(smq, "devinfo"); */
baAssert( strcmp("devinfo", (char*)msg) == 0 );
baAssert(smq_context.tids.device != 0); /* acks are in sequence */
smq_context.tids.dev_info_sub = smq_context.msg->ptid;
/* We have sufficient info for publishing the device
info message. All connected browsers will
receive this message and update their UI accordingly.
*/
send_dev_info(smq_context.tids.device, smq_context.tids.dev_info_sub);
}
break;
case SMQ_SUBCHANGE:
ZOS_LOG("Connected browsers: %d", smq_context.msg->status);
break;
/* Error codes */
case SMQE_DISCONNECT:
ZOS_LOG("Disconnect request from server");
smq_context.is_running = ZOS_FALSE;
return;
case SMQE_BUF_OVERFLOW:
/* no break */
default:
ZOS_LOG("Received code %d", smq_context.msg->status);
broker_reconnect();
return;
}
}
else if(msg_len > 0)
{
if(smq_context.msg->tid == smq_context.tids.display) /* topic "/m2m/led/display" */
{
/* Send device info to the new display unit: Send to
* browser's ephemeral ID (ptid).
*/
send_dev_info(smq_context.msg->ptid, smq_context.tids.dev_info_sub);
}
else if(smq_context.msg->tid == smq_context.msg->clientTid) /* sent to our ephemeral tid */
{
uint8_t led_data[2];
if(set_led(msg[0], msg[1]) != 0)
{
ZOS_LOG("ptid %X attempting to set invalid LED %d\n", smq_context.msg->ptid, msg[0]);
smq_context.is_running = ZOS_FALSE;
return;
}
/* Update all display units */
led_data[0] = msg[0];
led_data[1] = msg[1];
/* Publish to "/m2m/led/device", sub-topic "led" */
SMQ_publish(smq_context.msg, led_data, sizeof(led_data), smq_context.tids.device, smq_context.tids.led_sub);
}
else
{
ZOS_LOG("Received unknown tid %X\n", smq_context.msg->tid);
smq_context.is_running = ZOS_FALSE;
return;
}
}
else /* timeout */
{
break;
}
}
}
/*************************************************************************************************/
static void broker_reconnect(void)
{
SMQ_destructor(smq_context.msg);
SMQ_constructor(smq_context.msg, smq_context.msg_buf, sizeof(smq_context.msg_buf));
zn_event_issue(connect_to_broker_event_handler, NULL, 0);
}
/*************************************************************************************************/
static void button_event_handler(void *arg)
{
if(smq_context.msg != NULL)
{
uint8_t led_data[2];
const uint32_t led_id = (uint32_t)arg;
const zos_bool_t on = zn_gpio_get(led_info[led_id].platform_btn_gpio);
led_data[0] = (uint8_t)led_id;
led_data[1] = (U8)on;
/* Publish to "/m2m/led/device", sub-topic "led" */
SMQ_publish(smq_context.msg, led_data, sizeof(led_data), smq_context.tids.device, smq_context.tids.led_sub);
set_led(led_id, on); /* set the LED on/off */
}
}
/*************************************************************************************************
Send the device capabilities as JSON to the browser. Note: we could
have used our JSON library for creating the JSON, but the library
adds additional code. We have opted to manually craft the JSON
instead of using the JSON library. This keeps the code size
down. Manually crafting JSON data is easy, parsing JSON is not
easy. However, we have no need for parsing JSON in this demo.
You can optionally rewrite this code and use the following JSON
library: https://realtimelogic.com/products/json/
When you manually craft JSON, it can be good to use a JSON lint
parser if you should get a parse error in the browser. The JSON
lint parser will give you much better error reporting:
http://jsonlint.com/
*/
static int send_dev_info(uint32_t tid, uint32_t subtid)
{
char device_info[100];
SMQ *smq = smq_context.msg;
SMQ_wrtstr(smq, "{\"ipaddr\":\"");
SMQ_wrtstr(smq, smq_context.ipaddr);
SMQ_wrtstr(smq, "\",\"devname\":\"");
SMQ_wrtstr(smq, get_device_name(device_info));
SMQ_wrtstr(smq, "\",\"leds\":[");
/* Write JSON:
{
"id": number,
"name": string,
"color": string,
"on": boolean
}
*/
for(int i = 0 ; i < ARRAY_COUNT(led_info); ++i)
{
char buf[96];
char *ptr = buf;
const LedInfo *info = &led_info[i];
ptr += sprintf(ptr, "{\"id\":%d,", info->id);
ptr += sprintf(ptr, "\"name\":\"%s\",", (const char*)info->name);
ptr += sprintf(ptr, "\"color\":\"%s\",", led_type_to_str(info->color));
ptr += sprintf(ptr, "\"on\":%s},", zn_gpio_get(info->platform_led_gpio) ? "true" : "false");
if(i == ARRAY_COUNT(led_info)-1)
{
--ptr; // remove the trailing comma since this is the last element in the array
}
// null-terminate string
*ptr = 0;
SMQ_wrtstr(smq, buf);
}
SMQ_wrtstr(smq, "]}");
return SMQ_pubflush(smq, tid, subtid);
}
/*************************************************************************************************/
static const char* get_device_name(char *buffer)
{
strcpy(buffer, "SMQ Demo Device: ");
zn_settings_get_str("wl m", buffer + strlen(buffer), 18);
return buffer;
}
/*************************************************************************************************/
static int set_led(int ledId, int on)
{
if(ledId >= 0 && ledId < ARRAY_COUNT(led_info))
{
const LedInfo *info = &led_info[ledId];
ZOS_LOG("Set LED %d %s", ledId+1, on ? "on" : "off");
zn_gpio_set(info->platform_led_gpio, on);
return 0;
}
return -1;
}
/*************************************************************************************************/
static const char* led_type_to_str(LedColor t)
{
switch(t)
{
case LedColor_red: return "red";
case LedColor_yellow: return "yellow";
case LedColor_green: return "green";
case LedColor_blue: return "blue";
}
baAssert(0);
return "";
}