hurricane/security_camera/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 "common.h"
// Time in ms to debounce button presses
#define BUTTON_DEBOUNCE_TIME_MS 50
// Maximum assertion time in ms for the button to be considered 'clicked'
#define BUTTON_CLICK_TIME_MS 500
// Maximum network attempt failures before rebooting the system
#define MAX_NETWORK_ATTEMPTS 10
// Maximum numbfer of DMS check attempts before rebooting the system
#define MAX_DMS_CHECK_ATTEMPTS 5
// Holds the device webapp registratio code
char device_registration_code[5];
// Hurricane demo config
static hurricane_demo_t demo_info =
{
.demo_name = "Security Camera",
.start_demo_callback = start_demo_handler,
.stop_demo_callback = NULL,
.draw_splash_screen_callback = HURRICANE_SPLASH_DEFAULT_FUNCTION,
.demo_splash_screen_arg = NULL,
.splash_font_color = HURRICANE_SPLASH_DEFAULT_FONT_COLOR,
};
// System states
static enum
{
SYSTEM_STATE_CONNECT_TO_NETWORK,
SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION,
SYSTEM_STATE_CHECK_DMS_PRODUCT,
SYSTEM_STATE_SEND_IMAGES
} system_state;
static bool system_is_initialized;
static bool dms_connection_started;
static char product_code[64];
static int dms_check_attempt_counter;
/*************************************************************************************************/
void gos_app_init(void)
{
system_state = SYSTEM_STATE_CONNECT_TO_NETWORK;
dms_connection_started = false;
system_is_initialized = false;
dms_check_attempt_counter = 0;
hurricane_init(&demo_info);
// Register the DMS stream connection handler
gos_dms_set_message_stream_state_handler(dms_message_stream_connection_handler);
// Register the network event handler
}
/*************************************************************************************************
* This is called after the Hurricane component initializes
* This executes in the app thread
*/
static void start_demo_handler(void *arg)
{
gos_result_t result;
static const gos_button_config_t button_config =
{
.active_level = PLATFORM_BUTTON_ACTIVE_STATE,
.debounce = BUTTON_DEBOUNCE_TIME_MS,
.click_time = BUTTON_CLICK_TIME_MS,
.event_handler.press = NULL,
.event_handler.click = camera_trigger,
.event_handler.toggle = NULL,
.event_handler.changing = NULL,
.execution_context = GOS_BUTTON_CONTEXT_DEFAULT,
};
GOS_LOG("Starting security camera demo");
hurricane_display_set_font_color(GUI_RED);
hurricane_display_set_background_color(GUI_BLACK);
hurricane_display_write_msg(&GUI_Font16B_1, "Initializing system ...");
// Load the application settings
if (GOS_FAILED(result, settings_init()))
{
GOS_LOG("Failed to load settings, err:%d", result);
return;
}
// Discover the camera/GPS daughtercard
else if(GOS_FAILED(result, hurricane_discover_daughter_card(HURRICANE_CARD_CAMERA_GPS)))
{
GOS_LOG("Failed to discover the camera/GPS daughter card");
hurricane_display_write_msg(&GUI_Font16B_1, "Failed to discover the camera/GPS daughter card");
return;
}
// Initialize the camera
else if(GOS_FAILED(result, camera_init()))
{
GOS_LOG("Failed to initialize camera, err:%d", result);
hurricane_display_write_msg(&GUI_Font16B_1, "Failed to init camera: %d", result);
return;
}
// Initialize the sensors
else if(GOS_FAILED(result, sensor_init()))
{
GOS_LOG("Failed to initialize sensors, err:%d", result);
hurricane_display_write_msg(&GUI_Font16B_1, "Failed to init sensors: %d", result);
return;
}
system_is_initialized = true;
gos_button_init(PLATFORM_BUTTON2, &button_config, NULL);
process_system_event(NULL);
}
/*************************************************************************************************
* This is called when an event occurs in the system.
* Executes in app thread
*/
static void process_system_event(void *unused)
{
// If the system hasn't been initialized then just return
if(!system_is_initialized)
{
return;
}
if(!dms_connection_started && system_state > SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION)
{
system_state = SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION;
}
else if(dms_connection_started && system_state <= SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION)
{
system_state = SYSTEM_STATE_CHECK_DMS_PRODUCT;
}
if(system_state != SYSTEM_STATE_SEND_IMAGES)
{
camera_set_video_enabled(false);
}
switch(system_state)
{
case SYSTEM_STATE_CONNECT_TO_NETWORK:
connect_to_network();
break;
case SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION:
wait_for_dms_connection();
break;
case SYSTEM_STATE_CHECK_DMS_PRODUCT:
check_dms_product();
break;
case SYSTEM_STATE_SEND_IMAGES:
start_camera();
break;
default:
break;
}
}
/*************************************************************************************************
* This is called when the system should attempt to connect to the network
*/
static void connect_to_network(void)
{
static int network_attempt_counter = 0;
gos_result_t result;
// If the network is already up then update the state and return
{
system_state = SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION;
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
return;
}
hurricane_display_write_msg(&GUI_Font16B_1, "Connecting to network");
// Attempt to bring up the default network interface,
// blocking until the API return success or failure
{
// Network up failed
// If we failed because no SSID/password is set then automatically enter setup mode
if(interface == GOS_INTERFACE_WLAN)
{
{
hurricane_enter_setup_mode();
return;
}
}
hurricane_display_write_msg(&GUI_Font16B_1, "Network error: %d", result);
gos_event_register_timed(process_system_event, NULL, 3000, GOS_EVENT_FLAG_NONE);
if(++network_attempt_counter > MAX_NETWORK_ATTEMPTS)
{
GOS_LOG("Max network attempts exceeded, rebooting system");
}
}
else
{
system_state = SYSTEM_STATE_WAIT_FOR_DMS_CONNECTION;
network_attempt_counter = 0;
}
}
/*************************************************************************************************
* This checks if the DMS stream connection is opened
*/
static void wait_for_dms_connection(void)
{
{
system_state = SYSTEM_STATE_CONNECT_TO_NETWORK;
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
return;
}
if(!dms_connection_started)
{
hurricane_display_write_msg(&GUI_Font13_1, "Waiting for DMS connection\n\n" \
"Ensure:\n1. Device is claimed\n" \
"2. Device activated to user-product\n" \
"3. User-product has webhook to Firebase");
}
else
{
system_state = SYSTEM_STATE_CHECK_DMS_PRODUCT;
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
}
}
/*************************************************************************************************
* Once the DMS stream is opened, we use the stream to query the DMS and determine
* the 'product' the device is activated to.
*
* After issuing the request, check_dms_product_response_handler() will be called later
* if a response is received or the request times out.
*/
static void check_dms_product(void)
{
gos_result_t result;
uint8_t msgpack_buffer[64];
MSGPACK_INIT_WITH_BUFFER(msgpack, msgpack_buffer, sizeof(msgpack_buffer));
if(dms_check_attempt_counter > MAX_DMS_CHECK_ATTEMPTS)
{
GOS_LOG("Max DMS check attempts exceeded, rebooting system");
}
gos_msgpack_write_dict_str(&msgpack, "request", "device");
GOS_LOG("Sending 'device' request to DMS ...");
if(GOS_FAILED(result, gos_dms_message_write_buffer(msgpack_buffer, MSGPACK_BUFFER_USED(&msgpack), check_dms_product_response_handler, 5000)))
{
GOS_LOG("Failed to 'device' request to DMS, err:%d", result);
gos_event_register_timed(process_system_event, NULL, 5000, GOS_EVENT_FLAG_NONE);
++dms_check_attempt_counter;
}
}
/*************************************************************************************************
* This is called when a response from the DMS is received or the request times out
*/
static void check_dms_product_response_handler(void *ctx)
{
// If the request timed-out
if(ctx == NULL)
{
GOS_LOG("Timed-out waiting for response to 'device' request from DMS");
gos_event_register_timed(process_system_event, NULL, 5000, GOS_EVENT_FLAG_NONE);
++dms_check_attempt_counter;
return;
}
const gos_msgpack_object_dict_t *root = ctx;
const gos_msgpack_object_dict_t *device_info_obj = MSGPACK_DICT_DICT(root, "device");
if(device_info_obj != NULL)
{
gos_msgpack_object_str_t *str_obj = MSGPACK_DICT_STR(device_info_obj, "product");
if(str_obj != NULL)
{
MSGPACK_TO_STR(str_obj, product_code, sizeof(product_code));
str_toupper(product_code);
dms_check_attempt_counter = 0;
// Ensure the device is activated with a non-Silabs product
if(strncmp(product_code, "SILABS-", 7) == 0)
{
GOS_LOG("Device activated to SILABS product");
hurricane_display_write_msg(&GUI_Font13_1, "Must activate device to user-product in DMS.\n" \
"User-product must have webhook connected to Firebase");
gos_event_register_timed(process_system_event, NULL, 5000, GOS_EVENT_FLAG_NONE);
}
else
{
system_state = SYSTEM_STATE_SEND_IMAGES;
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
}
}
}
}
/*************************************************************************************************
* Start capturing images (if we aren't doing so already)
*/
static void start_camera(void )
{
if(!camera_is_enabled())
{
GOS_LOG("Starting camera");
camera_set_video_enabled(true);
display_registration_code();
}
}
/*************************************************************************************************
* Display the registration code on the LCD
*/
static void display_registration_code(void)
{
char mac_str[18];
// 0123456789ABCDEF0
// 00:0B:57:F4:87:82
device_registration_code[0] = mac_str[12];
device_registration_code[1] = mac_str[13];
device_registration_code[2] = mac_str[15];
device_registration_code[3] = mac_str[16];
device_registration_code[4] = 0;
GOS_LOG("\n\n*** Device registration code: %s\n\n", device_registration_code);
hurricane_display_lock();
hurricane_display_clear_all();
hurricane_display_set_font_color(GUI_WHITE);
hurricane_display_writef(5, 10, LCD_WIDTH-5, 15, GUI_TA_HCENTER| GUI_TA_VCENTER, &GUI_Font13B_1,
"Security Camera Active");
hurricane_display_set_font_color(GUI_RED);
hurricane_display_writef(5, 40, LCD_WIDTH-5, 15, GUI_TA_HCENTER| GUI_TA_VCENTER, &GUI_Font13_1,
"Registration code: %s", device_registration_code);
hurricane_display_writef(5, 55, LCD_WIDTH-5, 15, GUI_TA_HCENTER| GUI_TA_VCENTER, &GUI_Font13_1,
"Product: %s", product_code);
hurricane_display_set_font_color(GUI_DARKGRAY);
hurricane_display_writef(5, 85, LCD_WIDTH-5, 24, GUI_TA_HCENTER| GUI_TA_VCENTER, &GUI_Font8_1,
"Ensure the DMS product has a webhook connected to Firebase");
hurricane_display_set_font_color(HURRICANE_SPLASH_DEFAULT_FONT_COLOR);
hurricane_display_writef(LCD_WIDTH-50, LCD_HEIGHT-15, 48, 13, GUI_TA_RIGHT| GUI_TA_BOTTOM, &GUI_Font8_1,
"Trigger");
hurricane_display_set_font_color(GUI_RED);
hurricane_display_unlock();
gos_event_issue(update_next_image_timeout_handler, NULL, GOS_EVENT_FLAG_NONE);
}
/*************************************************************************************************
* This is called when the DMS stream connection is opened/closed
*/
static void dms_message_stream_connection_handler(bool is_up)
{
GOS_LOG("DMS stream connection: %s", is_up ? "up" : "down");
dms_connection_started = is_up;
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
}
/*************************************************************************************************
* This is called when the default network interface is brought up/down
*/
static void network_event_handler(bool is_up)
{
gos_event_issue(process_system_event, NULL, GOS_EVENT_FLAG_NONE);
}
/*************************************************************************************************
* This is periodically called to update the LCD with the amount of time
* until the next image is sent to the DMS.
*/
static void update_next_image_timeout_handler(void *arg)
{
#define GUI_POSITION 5, LCD_HEIGHT-20, 100, 15
if(camera_is_enabled())
{
const uint32_t remaining_ms = camera_get_ms_until_next_capture();
hurricane_display_lock();
hurricane_display_clear(GUI_POSITION);
hurricane_display_set_font_color(HURRICANE_SPLASH_DEFAULT_FONT_COLOR);
if(remaining_ms >= 10000)
{
hurricane_display_writef(GUI_POSITION, GUI_TA_LEFT| GUI_TA_BOTTOM, &GUI_Font8_1,
"Next: %ds", remaining_ms / 1000);
}
else
{
hurricane_display_writef(GUI_POSITION, GUI_TA_LEFT| GUI_TA_BOTTOM, &GUI_Font8_1,
"Next: %d.%01ds", remaining_ms / 1000, (remaining_ms % 1000) / 100);
}
hurricane_display_set_font_color(GUI_RED);
hurricane_display_unlock();
const uint32_t next_update_ms = (remaining_ms > 9000) ? 1000 : 100;
gos_event_register_timed(update_next_image_timeout_handler, NULL, next_update_ms, GOS_EVENT_FLAG_NONE);
}
}