network/http_server_stream/main.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
#include "gos.h"
#include "example_app_util.h"
#define APPLICATION_START_LINE "\r\n\r\nHTTP Server Stream API example starting..."
#define FLUSH_TIMEOUT_MS 7000
#define STREAM_LIST_TIMEOUT_MS 7000
#define STREAM_READ_TIMEOUT_MS 7000
#define STREAM_WRITE_PERIOD_MS 5000
#define STREAM_READ_PERIOD_MS 5000
#define MAX_WEBSOCKET_CLIENTS 3
static uint32_t ws_client_count;
/*************************************************************************************************/
void gos_app_init(void)
{
gos_result_t result;
GOS_LOG(APPLICATION_START_LINE);
// Load the application settings
if (GOS_FAILED(result, gos_load_app_settings("settings.ini")))
{
GOS_LOG("Failed to load settings, err:%d", result);
return;
}
// Register the streams for which this application listens
// Once the app listens for a stream, remote http clients can read/write the stream
gos_hs_stream_register("adc", adc_stream_handler, GOS_HS_STREAM_PERMISSION_READ, NULL);
gos_hs_stream_register("time", time_stream_handler, GOS_HS_STREAM_PERMISSION_READ, NULL);
gos_hs_stream_register("led", led_stream_handler, GOS_HS_STREAM_PERMISSION_ALL, NULL);
// Set the accept websocket callback
// This is invoked when a websocket client tries to connect
gos_hs_stream_ws_set_accept_callback(websocket_accept_callback);
// Set the websocket connect/disconnect handlers
// These handlers are invoked when a websocket client connects or disconnects
gos_hs_stream_ws_set_handlers(websocket_connect_handler, websocket_disconnect_handler);
// Initialize the platform default ADC
gos_adc_init(PLATFORM_STD_ADC, NULL);
// Initialize the platform default LED
gos_gpio_init(PLATFORM_STD_LED, GOS_GPIO_OUTPUT_PUSH_PULL, !PLATFORM_LED_ACTIVE_STATE);
// Attempt to bringup the network
example_app_util_network_up(GOS_INTERFACE_ANY, true, network_event_handler);
}
/*************************************************************************************************
* This is called when a remote client wants to read or write the 'led' stream.
* It executes in the app thread context.
*
* NOTE: This stream is registered with @ref GOS_HS_STREAM_PERMISSION_ALL permissions.
* - `out_context` will be non-null if the client is reading the stream
* - `in_data` will be non-null if the client is writing the stream
*
* Read/write packets have the following format:
* {
* timestamp: <UTC time string>,
* value: true/false
* }
*
*/
static void led_stream_handler(const char *stream_name, const gos_msgpack_object_t *in_data, gos_msgpack_context_t *out_context, void *arg)
{
// If the in data is non-null then the client is writing the stream
if(in_data != NULL)
{
GOS_LOG("[Client->Device] Write 'led' stream");
const gos_msgpack_object_str_t *timestamp_obj = MSGPACK_DICT_STR(in_data, "timestamp");
const gos_msgpack_object_str_t *value_obj = MSGPACK_DICT_STR(in_data, "value");
if(timestamp_obj != NULL && value_obj != NULL)
{
bool led_value;
char timestamp_str[32];
char value_str[8];
MSGPACK_STR(value_obj, value_str, sizeof(value_str));
MSGPACK_STR(timestamp_obj, timestamp_str, sizeof(timestamp_str));
if(strcmp(value_str, "high") == 0)
{
led_value = PLATFORM_LED_ACTIVE_STATE;
}
else if(strcmp(value_str, "low") == 0)
{
led_value = !PLATFORM_LED_ACTIVE_STATE;
}
else
{
led_value = !gos_gpio_get(PLATFORM_STD_LED);
}
GOS_LOG("[%s] setting LED to %d", timestamp_str, led_value);
gos_gpio_set(PLATFORM_STD_LED, led_value);
}
}
else
{
gos_result_t result;
GOS_LOG("[Client->Device] Read 'led' stream");
// The dictionary will have 2 fields
// Write timestamp
{
gos_msgpack_write_dict_str(out_context, "timestamp", (char*)&time_str);
}
// Write value
{
const bool value = (gos_gpio_get(PLATFORM_STD_LED) == PLATFORM_LED_ACTIVE_STATE);
gos_msgpack_write_dict_bool(out_context, "value", value);
}
// Flush the response to the client
if(GOS_FAILED(result, gos_hs_stream_flush(out_context, FLUSH_TIMEOUT_MS)))
{
GOS_LOG("[Client->Device] Failed to send 'led' stream response, err:%d", result);
}
}
}
/*************************************************************************************************
* This is called when a remote client wants to read the 'adc' stream.
* It executes in the app thread context.
*
* NOTE: This stream is registered with @ref GOS_HS_STREAM_PERMISSION_READ permissions only.
* Therefore only `out_context` will only be non-null
*
* This returns the following to the client:
* {
* timestamp: <UTC time string>,
* adc: [... 10 ADCs readings ]
* }
*
*/
static void adc_stream_handler(const char *stream_name, const gos_msgpack_object_t *in_data, gos_msgpack_context_t *out_context, void *arg)
{
gos_result_t result;
GOS_LOG("[Client->Device] Read 'adc' stream");
// The dictionary will have 2 fields
// Write timestamp
{
gos_msgpack_write_dict_str(out_context, "timestamp", (char*)&time_str);
}
// Read the ADC 10 times and write to context
{
// Add 10 element array
gos_msgpack_write_dict_array(out_context, "adc", 10);
for(int i = 10; i > 0; --i)
{
uint16_t reading;
gos_adc_sample(PLATFORM_STD_ADC, &reading, GOS_ADC_SAMPLE_TYPE_VOLTAGE);
gos_msgpack_write_uint(out_context, reading);
}
}
// Flush the response to the client
if(GOS_FAILED(result, gos_hs_stream_flush(out_context, FLUSH_TIMEOUT_MS)))
{
GOS_LOG("[Client->Device] Failed to send 'adc' stream response, err:%d", result);
}
}
/*************************************************************************************************
* This is called when a remote client wants to read the 'time' stream.
* It executes in the app thread context.
*
* NOTE: This stream is registered with @ref GOS_HS_STREAM_PERMISSION_READ permissions only.
* Therefore only `out_context` will only be non-null
*
* This returns the following to the client:
* {
* timestamp: <UTC time string>,
* rtc: <RTC time>,
* uptime: <uptime>
* }
*
*/
static void time_stream_handler(const char *stream_name, const gos_msgpack_object_t *in_data, gos_msgpack_context_t *out_context, void *arg)
{
gos_result_t result;
GOS_LOG("[Client->Device] Read 'time' stream");
// The dictionary will have 3 fields
// Write timestamp
{
gos_msgpack_write_dict_str(out_context, "timestamp", (char*)&time_str);
}
// Write rtc
{
gos_msgpack_write_dict_ulong(out_context, "rtc", rtc);
}
// Write uptime
{
uint64_t uptime;
gos_msgpack_write_dict_ulong(out_context, "uptime", uptime);
}
// Flush the response to the client
if(GOS_FAILED(result, gos_hs_stream_flush(out_context, FLUSH_TIMEOUT_MS)))
{
GOS_LOG("[Client->Device] Failed to send 'time' stream response, err:%d", result);
}
}
/*************************************************************************************************
* This is invoked just before a client connects.
*/
static bool websocket_accept_callback(void)
{
// Check if we have room for more clients
if(ws_client_count > MAX_WEBSOCKET_CLIENTS)
{
GOS_LOG("Max websocket clients connected");
return false;
}
else
{
return true;
}
}
/*************************************************************************************************
* This is invoked after a client connects
*/
static void websocket_connect_handler(void *handle)
{
gos_result_t result;
gos_msgpack_object_t *streams_obj;
ws_client_count += 1;
GOS_LOG("New client connected, connected client count: %d", ws_client_count);
GOS_LOG("Listing client streams ...");
if(GOS_FAILED(result, gos_hs_stream_ws_list(handle, &streams_obj, STREAM_LIST_TIMEOUT_MS)))
{
GOS_LOG("Failed to list client streams, err:%d", result);
}
else
{
int stream_count = MSGPACK_ARRAY_LENGTH(streams_obj);
GOS_LOG("Client stream count:%d", stream_count);
for(int i = 0; i < stream_count; ++i)
{
char stream_name[32];
gos_msgpack_object_str_t *name_obj = MSGPACK_ARRAY_STR(streams_obj, i);
MSGPACK_STR(name_obj, stream_name, sizeof(stream_name));
GOS_LOG(" %2d : %s", i, stream_name);
}
// Clean up the listing after receiving it
//GOS_LOG("Registering periodic handlers for client");
gos_event_register_periodic(websocket_periodic_write_version_stream_handler, handle, STREAM_WRITE_PERIOD_MS, GOS_EVENT_FLAG_RUN_NOW);
gos_event_register_periodic(websocket_periodic_read_sysinfo_stream_handler, handle, STREAM_READ_PERIOD_MS, GOS_EVENT_FLAG_RUN_NOW);
}
}
/*************************************************************************************************
* This is invoked when a client disconnects
*/
static void websocket_disconnect_handler(void *handle)
{
ws_client_count -= 1;
GOS_LOG("Client disconnected, connected client count: %d", ws_client_count);
GOS_LOG("Unregistering periodic handlers for client");
gos_event_unregister(websocket_periodic_write_version_stream_handler, handle);
gos_event_unregister(websocket_periodic_read_sysinfo_stream_handler, handle);
}
/*************************************************************************************************
* This is periodically invoked when the device should write the client
*
* This writes to the 'version' stream to the client with the following msg format:
* {
* timestamp: "<UTC str>",
* version: "<version str>"
* }
*/
static void websocket_periodic_write_version_stream_handler(void *handle)
{
gos_result_t result;
gos_msgpack_context_t *out_context;
GOS_LOG("[Device->Client] Writing version stream");
if(GOS_FAILED(result, gos_hs_stream_ws_write(handle, "version", &out_context)))
{
GOS_LOG("[Device->Client] Failed to initialize write context, err:%d", result);
return;
}
// The dictionary will have 2 fields
// Write timestamp
{
gos_msgpack_write_dict_str(out_context, "timestamp", (char*)&time_str);
}
// Write version
{
char version_str[128];
gos_msgpack_write_dict_str(out_context, "version", version_str);
}
// Flush the response to the client
if(GOS_FAILED(result, gos_hs_stream_flush(out_context, FLUSH_TIMEOUT_MS)))
{
GOS_LOG("[Device->Client] Failed to send 'version' stream write request, err:%d", result);
}
}
/*************************************************************************************************
* This is periodically invoked when the device should read the client
*
* This reads from the 'sysinfo' stream from the client and expects the following response:
* {
* timestamp: <UTC str>,
* os: <OS str>,
* platform: <platform str>
* }
*/
static void websocket_periodic_read_sysinfo_stream_handler(void *handle)
{
gos_result_t result;
GOS_LOG("[Device->Client] Reading sysinfo stream");
if(GOS_FAILED(result, gos_hs_stream_ws_read(handle, "sysinfo", &in_data, STREAM_READ_TIMEOUT_MS)))
{
GOS_LOG("[Device->Client] Failed to read stream 'sysinfo', err:%d", result);
return;
}
const gos_msgpack_object_str_t *timestamp_obj = MSGPACK_DICT_STR(in_data, "timestamp");
const gos_msgpack_object_str_t *platform_obj = MSGPACK_DICT_STR(in_data, "platform");
const gos_msgpack_object_str_t *os_obj = MSGPACK_DICT_STR(in_data, "os");
if(timestamp_obj != NULL && platform_obj != NULL && os_obj != NULL)
{
char timestamp_str[32];
char os_str[32];
char platform_str[32];
MSGPACK_STR(timestamp_obj, timestamp_str, sizeof(timestamp_str));
MSGPACK_STR(platform_obj, platform_str, sizeof(platform_str));
MSGPACK_STR(os_obj, os_str, sizeof(os_str));
GOS_LOG("[Device->Client] Timestamp:%s, OS: %s, platform:%s", timestamp_str, os_str, platform_str);
}
}
/*************************************************************************************************/
static void network_event_handler(bool is_up)
{
if(is_up)
{
char ip_str_buffer[32];
"wlan.network.ip" : "ethernet.network.ip", ip_str_buffer, sizeof(ip_str_buffer));
GOS_LOG("Network up");
GOS_LOG("\r\n\r\nRun the corresponding example Python script:");
GOS_LOG(" python example_client.py --host %s\r\n", ip_str_buffer);
GOS_LOG("OR\r\n");
GOS_LOG("Open a browser to:\r\n http://%s/http_stream.html\r\n\r\n", ip_str_buffer);
}
}