/*
 * emaPlay.c
 *
 * Created: 6/24/2019 11:59:16 AM
 * 
 *  ######################################################################################################
 *  DISCLAIMER:
 *  OptConnect Management, LLC provides this documentation in support of its products for the internal use
 *  of its current and prospective customers. The publication of this document does not create any other
 *  right or license in any party to use any content contained in or referred to in this document and any
 *  modification or redistribution of this document is not permitted. While efforts are made to ensure
 *  accuracy, typographical and other errors may exist in this document. OptConnect Management, LLC reserves
 *  the right to modify or discontinue its products and to modify this and any other product documentation
 *  at any time.
 * 
 *  All OptConnect products are sold subject to its published Terms and Conditions and subject to any
 *  separate terms agreed with its customers. No warranty of any type is extended by publication of this
 *  documentation, including, but not limited to, implied warranties of merchantability, fitness for a
 *  particular purpose and non-infringement.
 * 
 *  OptConnect is a trademark, and OptConnect ema is a trademark, of OptConnect Management, LLC. All
 *  trademarks, service marks and similar designations referenced in this document are the property of
 *  their respective owners
 *  #######################################################################################################
 * 
 *  version: 1.0
 * 
 *  History:
 *  Version     Description                                     Date
 *  1.0         Initial Release                                 5/19/2020
 */ 
 
#include "emaPlay.h"
#include "oc_leaf.h"
#include <math.h>

/*####### Timer Related Functionlaity #######*/
// these variables should NEVER be written to
uint32_t u32Sys1msTic;
uint32_t u32Sys1secTic;
uint32_t inlineTimer;  

static struct timer_task system_1ms_tick;
 
/* 
emaPlay_init_sys1ms_tick:
Initializes a timer that is used for a 1ms tick. The 1ms tick is then used by helper macros 
for creating/measuring delay when needed. 
*/
void emaPlay_init_sys1ms_tick( void )
{
    system_1ms_tick.interval = 1;
    system_1ms_tick.cb = (timer_cb_t)emaPlay_system_1ms_tick_cb;
    system_1ms_tick.mode = TIMER_TASK_REPEAT;
    timer_add_task(&TIMER_0, &system_1ms_tick);
    timer_start( &TIMER_0 );
}

/* 
emaPlay_system_1ms_tick_cb:
Interrupt callback function to increment the global tic variables that is used in the delay macros. 
*/
void emaPlay_system_1ms_tick_cb( void )
{
    u32Sys1msTic++;
    if ( (u32Sys1msTic % 1000) == 0 ) {
        u32Sys1secTic++;
    }

    debounce_inputs( );
    update_outputs( );
}
/*###########################################*/

/*####### Queue Related Functionlaity  ######*/
Q_STRUCT modem_uart_sync_rx_q, modem_uart_async_rx_q;
Q_STRUCT link_uart_sync_rx_q, link_uart_async_rx_q;
char modem_uart_sync_rx_q_buff[AT_RX_Q_BUFF_SIZE];
char modem_uart_async_rx_q_buff[AT_RX_Q_BUFF_SIZE];
char link_uart_sync_rx_q_buff[EMA_AT_RX_Q_BUFF_SIZE];
char link_uart_async_rx_q_buff[EMA_AT_RX_Q_BUFF_SIZE];

/* 
emaPlay_q_init:
Initializes a q that can be used for FIFO data storage. 
This function can also be called to reset the q to the initialized state. 
*/
void emaPlay_q_init( struct q_struct *q, char *buffer, unsigned buffer_size )
{
    setup_queue( q, buffer, buffer_size );
}

/* 
emaPlay_q_put:
Writes one byte(data) into an intialized q(*q). 
*/
int emaPlay_q_put( Q_STRUCT *q, char data )
{
    int q_st;

    q_st = char_into_queue(q, data);

    return q_st;
}

/* 
emaPlay_q_get:
Reads one byte(returns q_st) from an initilalized q(*q). Waits the specified timeout(timeout) 
before returning if the q(*q) is empty. 
*/
int emaPlay_q_get( Q_STRUCT *q, unsigned short timeout )
{
    int q_st;
    int timer = TIME_ELAPSED_ms(0);

    // wait for data to be available in the q
    do {
        q_st = char_outof_queue( q );
        // check timer expiry
        if ( TIME_ELAPSED_ms(timer) >= timeout ) {
            q_st = Q_ST_TIMEOUT;
            break;
        }
    } while ( q_st == Q_ST_EMPTY );

    return q_st;
}

/* 
emaPlay_q_check:
Checks a q(*q) for available data and returns the number of bytes(returns q_st) available. Waits a 
specified time(timeout) before checking the q. 
*/
int emaPlay_q_check( Q_STRUCT *q, unsigned short timeout )
{
    int q_st;

    // check for data after specified timeout
    if ( timeout ) {
        TIME_DELAY_ms(timeout);
    }
    q_st = num_chars_queued( q );

    return q_st;
}

/*###########################################*/

/*####### modem/emaLink UART Functionality ######*/
struct io_descriptor *ema_modem_uart_io;
struct io_descriptor *ema_mgmt_uart_io;
static uint8_t mod_uart_tx_complete, emaLink_uart_tx_complete;
static uint8_t mod_uart_sync_rx, emaLink_uart_sync_rx;

/* 
emaPlay_uart_setup: 
Completes the setup of an ema:Play UART(*const uart). The initialization occurs during the atmel_start_init() call. 
This function registers the interrupt callbacks(tx/rx_callback), sets the read/write descriptor(**io_rw). 
*/
void emaPlay_uart_setup( struct usart_async_descriptor *const uart, struct io_descriptor **io_rw, usart_cb_t tx_callback, usart_cb_t rx_callback )
{
    // register the interrupt callbacks for the async uart driver
    usart_async_register_callback( uart, USART_ASYNC_TXC_CB, tx_callback );
    usart_async_register_callback( uart, USART_ASYNC_RXC_CB, rx_callback );

    // get the io descriptor for writes/reads for this uart
    usart_async_get_io_descriptor( uart, io_rw );
}

/* 
emaPlay_modem_tx_cb: 
This callback is executed after a successful transmission of the uart write buffer. Since 
the hardware handles the byte interrupts, only a flag is set here once the buffer transmit is 
complete. 
*/
void emaPlay_modem_tx_cb ( void )
{
    mod_uart_tx_complete = 1;
}

/* 
emaPlay_modem_rx_cb:
This callback is executed after 1 to 16 btes of data has been received by the UART. Data can be 
recieved synchronous to a transmit or Asynchronously(URC), independent of a transmit. 
*/
void emaPlay_modem_rx_cb ( void )
{
    uint8_t rx_data[16];
    uint8_t len;

    // data received, save it in the q 
    if ( mod_uart_sync_rx ) {
        // this is sync rx data
        if (mod_uart_tx_complete) {
            while ( usart_async_is_rx_not_empty(&ema_modem_uart) ) {
                len = io_read(ema_modem_uart_io, rx_data, 16);
                for ( uint8_t i=0; i<len; i++ ) {
                    emaPlay_q_put( &modem_uart_sync_rx_q, rx_data[i] );
                }
            }
        }
    }else{
        // this is async(URC) rx data, save in different buffer
        while ( usart_async_is_rx_not_empty(&ema_modem_uart) ) {
            len = io_read(ema_modem_uart_io, rx_data, 16);
            for ( uint8_t i=0; i<len; i++ ) {
                emaPlay_q_put( &modem_uart_async_rx_q, rx_data[i] );
            }
        }
    }
}

/* 
emaPlay_modem_uart_send: 
Sends x(len) bytes of data contained in the supplied buffer(*buffer). Waits a specfied time(timeout) for the 
transmission to complete. Returns a status code. 
*/
int emaPlay_modem_uart_send( uint8_t *buffer, uint16_t len, uint16_t timeout, uint8_t flags )
{
    int st = UART_ST_TX_SUCCESS;
    uint32_t timer;
    mod_uart_sync_rx = 0;

    #if DBG_MODEM_AT
    char str[256];
    int l;
    l = sprintf(str, "\r\n%d: Module AT CMD:\r\n", (int)u32Sys1msTic);
    emaPlay_dbg_out( str, l );
    emaPlay_dbg_out( (char*)buffer, len );
    emaPlay_dbg_out( "\r\n", 2 );
    #if DBG_MODEM_AT_HEX
    // print msg in hex format
    l = 0;
    if ( (len/2) > sizeof(str) ) {
        emaPlay_dbg_out( "**HEX to long**", 15 );
    }else{
        for (int i = 0; i < len; i++) {
            l += sprintf(str+l, "%.2X", buffer[i] );
        }
        emaPlay_dbg_out( str, l );
    }
    emaPlay_dbg_out( "\r\n", 2 );
    #endif
    #endif

    // clear the tx complete flag and start the tx
    mod_uart_tx_complete = 0;
    timer = TIME_ELAPSED_ms(0);    
    io_write(ema_modem_uart_io, buffer, len);

    // poll the tx complete flag until the tx is complete or timeout
    while ( mod_uart_tx_complete == 0 ) {
        if (TIME_ELAPSED_ms(timer) >= timeout) {
            st = UART_ST_TIMEOUT;
            #if DBG_MODEM_AT
            emaPlay_dbg_out( "\r\ntx Timeout\r\n", 14 );
            #endif
            break;
        }
    }

    // if a sync rx is expected, then let the rx interrupt know
    if ( mod_uart_tx_complete == 1 ) {
        if ( flags & MODULE_FL_SYNC_RX ) {
            mod_uart_sync_rx = 1;
        }
    }

    return st;
}

/* 
emaPlay_modem_uart_receive: 
Recieves x(*len) amount of bytes and stores them in a supplied output buffer(*buffer). The number(*len) 
of bytes received is updated accordingly. Waits a specified time(timeout) before retreiving the data. 
A characte pattern(*pattern) is passed in to compare for proper msg reception. 
*/
int emaPlay_modem_uart_receive( uint8_t *buffer, uint16_t *len, uint16_t timeout, uint8_t flags, const char *pattern )
{
    static int st = UART_ST_TX_SUCCESS;
    int i;
    uint32_t timer;
    char *p;

    timer = TIME_ELAPSED_ms(0);
    i = 0;
    do {
        // check for timeout
        TIME_DELAY_ms(2);
        if ( TIME_ELAPSED_ms(timer) >= timeout ) {
            st = Q_ST_TIMEOUT;
            break;
        }
        // check for data
        st = emaPlay_q_check(&modem_uart_sync_rx_q, 0);
        if ( st > 0 ) {
            // data ready, retrieve it
            while ( st ) {
                *(buffer+i) = (uint8_t)emaPlay_q_get( &modem_uart_sync_rx_q, 5 );
                i++;
                st--;
            }
        }
        // check for desired pattern
        p = strstr( (char*)buffer, pattern );
        if ( p ) {
            // get remaining data if available
            TIME_DELAY_ms(20)
            st = emaPlay_q_check(&modem_uart_sync_rx_q, 0);
            if ( st > 0 ) {
                while ( st ) {
                    *(buffer+i) = (uint8_t)emaPlay_q_get( &modem_uart_sync_rx_q, 5 );
                    i++;
                    st--;
                }
            }
            st = UART_ST_TX_SUCCESS;
        }
    } while ( p == 0 );


    #if DBG_MODEM_AT
    char str[256];
    int l;
    if ( st == UART_ST_TX_SUCCESS ) {
        l = sprintf(str, "\r\n%d: Module AT RESPONSE:\r\n", (int)u32Sys1msTic);
        emaPlay_dbg_out( str, l );
        emaPlay_dbg_out( (char*)buffer, i );
        emaPlay_dbg_out( "\r\n", 2 );
        #if DBG_MODEM_AT_HEX
        // print msg in hex format
        l = 0;
        if ( (i/2) > sizeof(str) ) {
            emaPlay_dbg_out( "**HEX to long**", 15 );
        }else{
            for (int j = 0; j < i; j++) {
                l += sprintf(str+l, "%.2X", buffer[j] );
            }
            emaPlay_dbg_out( str, l );
        }
        emaPlay_dbg_out( "\r\n", 2 );
        #endif
    }else{
        // print any junk/data/error msgs received
        l = sprintf(str, "\r\n%d: Module AT RESPONSE:\r\n", (int)u32Sys1msTic);
        emaPlay_dbg_out( str, l );
        emaPlay_dbg_out( "\r\nrx Timeout\r\n", 14 );
        if ( i ) {
            emaPlay_dbg_out((char *)buffer, i);
            emaPlay_dbg_out( "\r\n", 2 );
        }
    }
    #endif

    // clear the sync rx flag
    mod_uart_sync_rx = 0;

    *len = i;
    return st;
}

/* 
void emaPlay_emaLink_tx_cb( void ) 
This callback is executed after a successful transmission of the emaLink uart write buffer. Since 
the hardware handles the byte interrupts, only a flag is set here once the buffer transmit is 
complete. 
*/
void emaPlay_emaLink_tx_cb ( void )
{
    emaLink_uart_tx_complete = 1;
}

/* 
void emaPlay_emaLink_rx_cb( void ) 
This callback is executed after 1 to 16 btes of data has been received by the emaLink UART. Data can be 
recieved synchronous to a transmit or Asynchronously(URC), independent of a transmit. 
*/
void emaPlay_emaLink_rx_cb ( void )
{
    uint8_t rx_data[16];
    uint8_t len;

    // data received, save it in the q 
    if ( emaLink_uart_sync_rx ) {
        // this is sync rx data
        if (emaLink_uart_tx_complete) {
            while ( usart_async_is_rx_not_empty(&ema_mgmt_uart) ) {
                len = io_read(ema_mgmt_uart_io, rx_data, 16);
                for ( uint8_t i=0; i<len; i++ ) {
                    emaPlay_q_put( &link_uart_sync_rx_q, rx_data[i] );
                }
            }
        }
    }else{
        // this is async(URC) rx data, save in different buffer
        while ( usart_async_is_rx_not_empty(&ema_mgmt_uart) ) {
            len = io_read(ema_mgmt_uart_io, rx_data, 16);
            for ( uint8_t i=0; i<len; i++ ) {
                emaPlay_q_put( &link_uart_async_rx_q, rx_data[i] );
            }
        }
    }
}

/* 
emaPlay_emaLink_uart_send: 
Sends x(len) bytes of data contained in the supplied buffer(*buffer). Waits a specfied time(timeout) for the 
transmission to complete. Returns a status code. 
*/
int emaPlay_emaLink_uart_send( uint8_t *buffer, uint16_t len, uint16_t timeout, uint8_t flags )
{
    int st = UART_ST_TX_SUCCESS;
    uint32_t timer;
    emaLink_uart_sync_rx = 0;

    // clear the tx complete flag and start the tx
    #if DBG_EMA_LINK_AT
    char str[64];
    int l;
    l = sprintf(str, "\r\n%d: ema AT CMD:\r\n", (int)u32Sys1msTic);
    emaPlay_dbg_out( str, l );
    emaPlay_dbg_out( (char*)buffer, len );
    emaPlay_dbg_out( "\r\n", 2 );
    // print msg in hex format
    #if DBG_EMA_LINK_AT_HEX
    l = 0;
    for ( int i=0; i<len; i++ ) {
        l += sprintf(str+l, "%.2X", buffer[i] );
    }
    emaPlay_dbg_out( str, l );
    emaPlay_dbg_out( "\r\n", 2 );
    #endif
    #endif

    // send the data
    emaLink_uart_tx_complete = 0;
    timer = TIME_ELAPSED_ms(0);    
    io_write(ema_mgmt_uart_io, buffer, len);

    // poll the tx complete flag until the tx is complete or timeout
    while ( emaLink_uart_tx_complete == 0 ) {
        if (TIME_ELAPSED_ms(timer) >= timeout) {
            st = UART_ST_TIMEOUT;
            break;
        }
    }

    // if a sync rx is expected, then let the rx interrupt know
    if ( emaLink_uart_tx_complete == 1 ) {
        if ( flags & EMA_LINK_FL_SYNC_RX ) {
            emaLink_uart_sync_rx = 1;
        }
    }

    return st;
}

/* 
emaPlay_emaLink_uart_receive: 
Recieves x(*len) amount of bytes and stores them in a supplied output buffer(*buffer). The number(*len) 
of bytes received is updated accordingly. Waits a specified time(timeout) before retreiving the data. A character 
pattern(*pattern) is passed in to compare against for proper msg reception. 
*/
int emaPlay_emaLink_uart_receive( uint8_t *buffer, uint16_t *len, uint16_t timeout, const char *pattern )
{
    static int st = UART_ST_TX_SUCCESS;
    int i;
    uint32_t timer;
    char *p;

    timer = TIME_ELAPSED_ms(0);
    i = 0;
    do {
        // check for timeout
        TIME_DELAY_ms(2);
        if ( TIME_ELAPSED_ms(timer) >= timeout ) {
            st = Q_ST_TIMEOUT;
            break;
        }
        // check for data
        st = emaPlay_q_check(&link_uart_sync_rx_q, 0);
        if ( st > 0 ) {
            // data ready, retrieve it
            while ( st ) {
                *(buffer+i) = (uint8_t)emaPlay_q_get( &link_uart_sync_rx_q, 5 );
                i++;
                st--;
            }
        }
        // check for desired pattern
        p = strstr( (char*)buffer, pattern );
        if ( p ) {
            st = UART_ST_TX_SUCCESS;
        }
    } while ( p == 0 );


    #if DBG_EMA_LINK_AT
    if ( st == UART_ST_TX_SUCCESS ) {
        char str[512];
        int l;
        l = sprintf(str, "\r\n%d, ema AT RESPONSE, Val=,\r\n", (int)u32Sys1msTic);
        emaPlay_dbg_out( str, l );
        emaPlay_dbg_out( (char*)buffer, i );
        emaPlay_dbg_out( "\r\n", 2 );
        // print msg in hex format
        #if DBG_EMA_LINK_AT_HEX
        l = 0;
        if ( i > sizeof(str) ) {
            emaPlay_dbg_out( "**HEX to long**", 15 );
        }else{
            for (int j = 0; j < i; j++) {
                l += sprintf(str+l, "%.2X", buffer[j] );
            }
            emaPlay_dbg_out( str, l );
        }
        emaPlay_dbg_out( "\r\n", 2 );
        #endif
    }else{
        emaPlay_dbg_out( "\r\nrx Timeout\r\n", 14 );
    }
    #endif

    // clear the sync rx flag
    emaLink_uart_sync_rx = 0;

    *len = i;
    return st;
}

/*###########################################*/

#ifdef DEBUG
/*####### emaPlay Debug Functionality ######*/
struct io_descriptor *emaPlay_dbg_uart_io;
Q_STRUCT dbg_rx_q, dbg_tx_q;
char dbg_q_rx_buff[DBG_Q_BUFF_SIZE];
char dbg_q_tx_buff[DBG_Q_BUFF_SIZE];
static uint8_t dbg_uart_tx_complete;

/* 
void emaPlay_dbg_tx_cb( void ) 
*/
void emaPlay_dbg_tx_cb ( void )
{
    dbg_uart_tx_complete = 1;
}

/* 
void emaPlay_dbg_rx_cb( void ) 
*/
void emaPlay_dbg_rx_cb ( void )
{
    uint8_t rx_data[16];
    uint8_t len;

    while ( usart_async_is_rx_not_empty(&emaPlay_debug_uart) ) {
        len = io_read(emaPlay_dbg_uart_io, rx_data, 16);
        for ( uint8_t i=0; i<len; i++ ) {
            emaPlay_q_put( &dbg_rx_q, rx_data[i] );
        }
    }
}

/* 
void emaPlay_dbg_out( void )
*/
void emaPlay_dbg_out( char *dbg_data, uint16_t len )
{
    int i;

    // stuff the debug out data into the dbg tx q
    for ( i=0; i<len; i++ ) {
        emaPlay_q_put( &dbg_tx_q, dbg_data[i] );
    }
}

/* 
emaPlay_dbg_uart_send: 
*/
int emaPlay_dbg_uart_send( char *buffer, uint16_t len, uint16_t timeout, uint8_t flags )
{
    int st = UART_ST_TX_SUCCESS;
    uint32_t timer = TIME_ELAPSED_ms(0);

    // clear the tx complete flag and start the tx
    dbg_uart_tx_complete = 0;
    io_write(emaPlay_dbg_uart_io, (uint8_t*)buffer, len);

    // poll the tx complete flag until the tx is complete or timeout
    while ( dbg_uart_tx_complete == 0 ) {
        if (TIME_ELAPSED_ms(timer) >= timeout) {
            st = UART_ST_TIMEOUT;
            break;
        }
    }

    return st;
}

void emaPlay_app_debug_send( char *buffer, uint16_t len )
{
    #if DBG_APPLICATION
    emaPlay_dbg_out(buffer, len);
    #endif
}

/* 
void dbg_task( void )
This task is used to send and receive debug console data
*/
char help0[] = "\r\n\r\n------ Cmd Menu ------\r\n";
char help1[] = "'d' = ema:Play and ema status data\r\n's' = Start/Stop demo\r\n";
//char help2[] = "\r\n'p' = toggle ema power\r\n'r' = reset module via pin\r\n'c' = set PDP context(AT&T/VZW)\r\n'h' = HTTP query";
//char help3[] = "\r\n'a' = ema validation test(AT&T pri.)\r\n'v' = ema validation test(VZW pri.)\r\n's' = socket status\r\n";
void dbg_task ( struct emaLink_context *link_handle, 
                struct modem_at_context *at_handle,
                struct ema_status *ema, 
                struct emaPlay_status *emaPlay )
{
    int data, i, l;
    char tx_buff[DBG_Q_BUFF_SIZE];
    char str[128];
    uint8_t input;
    //static unsigned char glPDPCntx = ATT_CONTEXT_ID;

    // send all available data
    while ( (data = emaPlay_q_check( &dbg_tx_q, 0 )) != 0 ) {
        for ( i=0; i<data; i++ ) {
            tx_buff[i] = emaPlay_q_get( &dbg_tx_q, 5 );
        }
        emaPlay_dbg_uart_send( tx_buff, data, 100, 0 );
    }

    // check for any input
    data = emaPlay_q_check( &dbg_rx_q, 0 );
    if ( data ) {
        // get the data and process it
        input = emaPlay_q_get( &dbg_rx_q, 5 );
        switch ( input ) {
        case 's':
            // simulate a sw7 press
            sw0_long_press = 1;

            break;
        case '?': 
            emaPlay_dbg_uart_send( help0, sizeof(help0), 50, 0 );
            emaPlay_dbg_uart_send( help1, sizeof(help1), 50, 0 );
            //emaPlay_dbg_uart_send( help2, sizeof(help2), 50, 0 );
            //emaPlay_dbg_uart_send( help3, sizeof(help3), 50, 0 );
            break;
        case 'd': 
            // print the emaPlay and ema status structs
            // ema
            l = sprintf( str, "\r\n\r\n%d mS\r\n----ema status----\r\n", (int)u32Sys1msTic );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "uptime:         %d\r\n", (int)ema->uptime );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "pri. carrier:   %s\r\n", ema->pri_carrier );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "act. carrier:   %s\r\n", ema->act_carrier );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "cell fw ver:    %s\r\n", ema->cell_fw );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "failover:       %s\r\n", ema->failover_st );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "failover time:  %d\r\n", (int)ema->failover_time_ago );
            emaPlay_dbg_uart_send(str, l, 50, 0);


            l = sprintf( str, "context ID:     %d\r\n", ema->cntx_id );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "context status: %d\r\n", ema->cntx_st );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "acc tech:       %s\r\n", ema->acc_tech );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "reg status:     %d\r\n", ema->reg );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "calc sig q:     %d\r\n", ema->calc_sig_q );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "mobile ip:      %s\r\n", ema->IP );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "board ID:       %s\r\n", ema->board_id );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "ema fw ver:     %s\r\n", ema->fw_ver );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "sn:             %s\r\n", ema->sn );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "ema model:      %s\r\n", ema->model );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "OC Services:    %s\r\n", ema->oc_services_state );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            // emaPlay
            emaPlay_convert_to_decimal( &emaPlay->stats );
            l = sprintf( str, "\r\n----emaPlay status----\r\n" );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "temp:           %d.%df\r\n", emaPlay->stats.t_l, emaPlay->stats.t_r );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "rel hum:        %d.%d%%\r\n", emaPlay->stats.h_l, emaPlay->stats.h_r );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "light ADC:      %d (12 bit)\r\n", emaPlay->stats.light );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "ema V:          %d.%dV\r\n", emaPlay->stats.eV_l, emaPlay->stats.eV_r );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "sys V:          %d.%dV\r\n", emaPlay->stats.sV_l, emaPlay->stats.sV_r );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "user ADC:       %d (12 bit)\r\n", emaPlay->stats.user_adc );
            emaPlay_dbg_uart_send(str, l, 50, 0);

            l = sprintf( str, "sw 1 state:     %d\r\n", emaPlay->events[EV_SW0_PRESS_RELEASE] );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "sw 2 state:     %d\r\n", emaPlay->events[EV_SW1_PRESS_RELEASE] );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "din 1 state:    %d\r\n", emaPlay->events[EV_DIN0_STATE] );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "din 2 state:    %d\r\n", emaPlay->events[EV_DIN1_STATE] );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "led 1 duty      %d (mS)\r\n", emaPlay->user_douts[0].duty );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "led 1 period    %d (mS)\r\n", emaPlay->user_douts[0].period );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "led 2 duty      %d (mS)\r\n", emaPlay->user_douts[1].duty );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "led 2 period    %d (mS)\r\n", emaPlay->user_douts[1].period );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "dout 1 duty     %d (mS)\r\n", emaPlay->user_douts[2].duty );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "dout 1 period   %d (mS)\r\n", emaPlay->user_douts[2].period );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "dout 2 duty     %d (mS)\r\n", emaPlay->user_douts[3].duty );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "dout 2 period   %d (mS)\r\n", emaPlay->user_douts[3].period );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "sn:             " );
            for ( i=0; i<EMA_PLAY_SN_SIZE; i++) {
                l += sprintf( str + l, "%.2X", emaPlay->sn[i] );
            }
            l += sprintf( str+l, "\r\n" );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "leaf init:      %d\r\n", emaPlay->leaf_init );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "auto reboots    %d\r\n", emaPlay->auto_module_reboots );
            emaPlay_dbg_uart_send(str, l, 50, 0);
            l = sprintf( str, "manual reboots  %d\r\n\r\n", emaPlay->man_ema_reboots );
            emaPlay_dbg_uart_send(str, l, 50, 0);
        break;
        default:
            break;
        }
    }
}
#else
void emaPlay_app_debug_send( char *buffer, uint16_t len )
{
   
}
#endif

/*###########################################*/

/*##########  AT cmd Functionality ##########*/
/* 
emaPlay_modem_at_query:
Sends and receives an AT transaction/query over the modem AT interface. The tx and rx buffers are passed in as part of the *at_query argument. 
This function clears the receive buffer, calculates the length of the AT cmd, updates any flags, sends the AT 
cmd, and waits for a response. The expected response character pattern(*res_pattern) is passed in and a status code is returned. 
*/
int emaPlay_modem_at_query( struct modem_at_context *at_query, unsigned short tx_timeout, unsigned short res_timeout, const char *res_pattern )
{
    int at_st;
    uint16_t i;

    // flush the rx buffer
    memset( at_query->response, 0, sizeof(at_query->response) );

    // figure out the length of the at cmd/data
    if ( at_query->data_length == 0 ) {
        if (at_query->at_cmd_flags & MODULE_FL_SPECIAL_CMD) {
            at_query->at_cmd_flags &= ~MODULE_FL_SPECIAL_CMD;
            for (i = 0;; ++i) {
                if (*(at_query->at_cmd+i) == SEND_DATA_CONFIRM ) {
                    break;
                }
            }
        }else{
            for (i = 0;; ++i) {
                if (*(at_query->at_cmd+i) == '\r' ) {
                    break;
                }
            }
        }
        i++;
    }else{
        i = at_query->data_length;
    }

    // a sync rx(response) is always expected for a query
    at_query->at_cmd_flags |= MODULE_FL_SYNC_RX;

    // send/recieve the at query, only if the uart interface is available
    if ( at_query->status & ST_MODULE_INTERFACE_READY ) {
        at_st = emaPlay_modem_uart_send((uint8_t *)at_query->at_cmd, i, tx_timeout, at_query->at_cmd_flags);
        if (at_st == UART_ST_TX_SUCCESS) {
            at_st = emaPlay_modem_uart_receive((uint8_t *)at_query->response, &at_query->response_length, res_timeout, at_query->response_flags, res_pattern );
        }
    }else{
        at_st = UART_ST_INTERFACE_DOWN;
    }

    // clear all flags
    at_query->at_cmd_flags = 0;
    at_query->response_flags = 0;
    at_query->data_length = 0;

    return at_st;
}

/* 
emaPlay_emaLink_at_query:
Sends and receives an AT transaction/query over the emaLink interface. The tx and rx buffers are passed in as part of the at_query argument. 
This function clears the receive buffer, calucaltes the length of the AT cmd, updates any flags, sends the AT 
cmd, and waits for a response. A status code is returned. 
*/
int emaPlay_emaLink_at_query( struct emaLink_context *at_query )
{
    int at_st;
    uint16_t i;

    // flush the rx buffer
    memset( at_query->response, 0, sizeof(at_query->response) );

    // figure out the length of the at cmd;
    for ( i=0;;++i ) {
        if (*(at_query->at_cmd+i) == '\r' ) {
            break;
        }
    }
    i++;

    // a sync rx(response) is always expected for a query
    at_query->at_cmd_flags |= EMA_LINK_FL_SYNC_RX;

    // send/recieve the at query
    at_st = emaPlay_emaLink_uart_send( (uint8_t*)at_query->at_cmd, i, 50, at_query->at_cmd_flags );
    if (at_st == UART_ST_TX_SUCCESS) {
        at_st = emaPlay_emaLink_uart_receive( (uint8_t*)at_query->response, &at_query->response_length, 10000, "\r\nOK\r\n" );
    }

    return at_st;
}

extern int CheckForMsg( char *buff, unsigned short len, int *loc );
/* 
emaPlay_link_URC_handler:
Checks for and processes any Asynchronous msgs/URCs that have been received from ema over the emaLink interface. This function must be 
called periodically, at a faster rate (< 50 mS). Also, the user can insert code into this function to process the URCs. Also, the input 
parameters can be modified as needed. 
*/
void emaPlay_emaLink_URC_handler( struct emaLink_context *emaLink_handle, struct modem_at_context *modem_handle ) 
{
    int st, urc, msg;
    static char urc_buff[EMA_AT_CMD_RESPONSE_MAX_SIZE];
    int loc = 0;
    static int len = 0;

    // check to see if any async data is available
    st = emaPlay_q_check( &link_uart_async_rx_q, 0 );
    if ( st > 0 ) {
        // we have async data, pull data out of the q until we've received a full msg, indicated by 2 pairs of "\r\n"
        msg = 0;
        loc = 0;
        while ( msg == 0 && st ) {
            urc_buff[len] = emaPlay_q_get( &link_uart_async_rx_q, 5 );
            len++;
            st--;
            msg = CheckForMsg( urc_buff, len, &loc );
        }

        // process a the msg if found
        if ( msg ) {
            urc = emaLinkURC_GetValue( urc_buff, len );
            if ( urc != EMA_LINK_URC_INVALID ) {

                #if DBG_EMA_LINK_URC
                char str[256];
                int l;
                l = sprintf(str, "\r\n%d, ema URC, Val=%d, ", (int)u32Sys1msTic, urc);
                emaPlay_dbg_out( str, l );
                emaPlay_dbg_out( urc_buff, len );
                emaPlay_dbg_out( "\r\n", 2 );
                #if DBG_EMA_LINK_URC_HEX
                // print msg in hex format
                l = 0;
                for ( int i=0; i<len; i++ ) {
                    l += sprintf(str+l, "%.2X", urc_buff[i] );
                }
                emaPlay_dbg_out( str, l );
                emaPlay_dbg_out( "\r\n", 2 );
                #endif
                #endif

                // we have a urc process them according to the user application
                emaPlay_emaLink_process_URC( emaLink_handle, modem_handle, urc, urc_buff, len );
            }
            len = 0;
        }
    }
}

/* 
emaPlay_emaLink_process_URC:
Processes any Asynchronous msgs/URCs that have been received from ema over the emaLink interface. This function can be modified according 
to the users application. 
*/
void emaPlay_emaLink_process_URC( struct emaLink_context *link_handle, struct modem_at_context *modem_handle, int urc, char *msg, unsigned short len )
{
    // process the urc according to the application & hardware
    switch ( urc ) {
    case EMA_LINK_URC_AT_READY:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_AT_READY;
        break;
    case EMA_LINK_URC_POWERING_ON:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_POWERING_ON;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_READY:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_READY;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_POWERING_OFF:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_READY;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_ON;
        break;
    case EMA_LINK_URC_POWERED_OFF:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_POWERED_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_READY;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_ON;
        break;
    case EMA_LINK_URC_MODULE_UART_ESTABLISHED:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_UART_READY;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_UART_DOWN;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        // module
        modem_handle->status |= ST_MODULE_INTERFACE_READY;
        break;
    case EMA_LINK_URC_MODULE_UART_REMOVED:
        // emaLink
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_UART_DOWN;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_UART_READY;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        // module
        modem_handle->status &= ~ST_MODULE_INTERFACE_READY;
        break;
    case EMA_LINK_URC_MODULE_RESET_REQUESTED:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_RESET_REQ;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_MODULE_RESETTING:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_RESETTING;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_RESET_SHORT;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_MODULE_RESET_SUCCESS:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_RESET;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_MODULE_RESET_NOT_OBSERVED:
        link_handle->urc_status |= EMA_LINK_CNTX_MODULE_RESET_SHORT;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_RESET_REQ;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_RESETTING;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_RESET;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
        link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERED_OFF;
        break;
    case EMA_LINK_URC_EMA_REBOOTING_60_SECONDS:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_REBOOTING_60_SECONDS;
        break;
    case EMA_LINK_URC_EMA_REBOOTING_NOW:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_REBOOTING_NOW;
        link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_AT_READY;
        break;
    case EMA_LINK_URC_EMA_BOARD_NOTIFY:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_BOARD_NOTIFY;
        // pull out the the board notify msg
        emaLinkURC_GetMsg( link_handle, msg );
        break;
    case EMA_LINK_URC_EMA_MICROFOTA_STARTING:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_MICROFOTA_STARTED;
        link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_COMPLETE;
        break;
    case EMA_LINK_URC_EMA_MICROFOTA_COMPLETE:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_MICROFOTA_COMPLETE;
        link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_STARTED;
        break;
    case EMA_LINK_URC_EMA_MICROFOTA_FAILED:
        link_handle->urc_status |= EMA_LINK_CNTX_EMA_MICROFOTA_FAILED;
        link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_STARTED;
        link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_COMPLETE;
        break;
    default:
        break;
    }

}

/* 
emaPlay_checkURC_any:
Checks to see if any URC's are set against a passed in bitmask(URCMask) 
*/
uint8_t emaPlay_checkURC_any( struct emaLink_context *emaLink_handle, uint32_t URCMask )
{
    uint8_t ret = 0;

    // check if any are set
    if ( emaLink_handle->urc_status & URCMask ) {
        ret = 1;
    }

    return ret;
}

/* 
emaPlay_checkURC_mask:
Checks to see if all URC's are set against a passed in bitmask(URCMask) 
*/
uint8_t emaPlay_checkURC_mask( struct emaLink_context *emaLink_handle, uint32_t URCMask )
{
    uint8_t ret = 0;

    // check if all are set
    if ( (emaLink_handle->urc_status & URCMask) == URCMask ) {
        ret = 1;
    }

    return ret;
}

/*###########################################*/


/*###### ema:Play GPIO Functionality  #######*/

/* 
emaPLay_ema_on_off_control: 
Controls ema's on_off pin. 
*/
void emaPlay_ema_on_off_control( enum ema_on_off_state state )
{
    switch ( state ) {
    case EMA_ON_OFF_ST_ON:
        gpio_set_pin_level( ema_on_off, true );
        break;
    case EMA_ON_OFF_ST_OFF:
        gpio_set_pin_level( ema_on_off, false );
        break;
    case EMA_ON_OFF_ST_TOGGLE:
        gpio_toggle_pin_level( ema_on_off );
        break;
    default:
        gpio_set_pin_level( ema_on_off, false );
        break;
    }
}

/* 
emaPlay_ema_power_control 
Controls ema:Play's load switch(U6) which is used to apply power to ema.  
*/
void emaPlay_ema_power_control( enum ema_power_state state )
{
    switch ( state ) {
    case EMA_POWER_ST_ON:
        gpio_set_pin_level( mod4V_dis, false );
        // enable uarts
        usart_async_enable( &ema_modem_uart );
        usart_async_enable( &ema_mgmt_uart );
        break;
    case EMA_POWER_ST_OFF:
        gpio_set_pin_level( mod4V_dis, true );
        // disable uarts
        usart_async_disable( &ema_modem_uart );
        usart_async_disable( &ema_mgmt_uart );
        // flush uart buffers
        emaPlay_q_init( &modem_uart_sync_rx_q, modem_uart_sync_rx_q_buff, sizeof(modem_uart_sync_rx_q_buff) );
        emaPlay_q_init( &modem_uart_async_rx_q, modem_uart_async_rx_q_buff, sizeof(modem_uart_async_rx_q_buff) );
        emaPlay_q_init( &link_uart_sync_rx_q, link_uart_sync_rx_q_buff, sizeof(link_uart_sync_rx_q_buff) );
        emaPlay_q_init( &link_uart_async_rx_q, link_uart_async_rx_q_buff, sizeof(link_uart_async_rx_q_buff) );
        break;
    case EMA_POWER_ST_TOGGLE:
        gpio_toggle_pin_level( mod4V_dis );
        break;
    default:
        gpio_set_pin_level( mod4V_dis, false );
        break;
    }
}

/* 
emaPlay_ema_reset: 
Controls ema's nRESET_REQ pin. A delay(reset_time) is inserted between the gpio pin toggle. 
*/
void emaPlay_ema_reset( uint16_t reset_time )
{
    // reset ema using the reset pin
    gpio_set_pin_level(ema_reset, true);
    TIME_DELAY_ms( reset_time );
    gpio_set_pin_level(ema_reset, false);
}
/*###########################################*/

/*###### ema:Play App level functions  #######*/
uint8_t sw0_pending, sw0_long_press, sw1_pending;
uint8_t din0_state, din1_state;
uint8_t sts_state;

/* 
emaPlay_get_chip_SN: 
Reads the atsame5x(u16) unique serial number  
*/
void emaPlay_get_chip_SN( char *sn )
{
    // get the sn from the chip
    memcpy( sn, (uint8_t*)SN_LOC_0, 4 );
    memcpy( sn+4, (uint8_t*)SN_LOC_1, 4 );
    memcpy( sn+8, (uint8_t*)SN_LOC_2, 4 );
    memcpy( sn+12, (uint8_t*)SN_LOC_3, 4 );
}

/* 
emaPlay_check_events: 
Checks for any ema:Play input events. Input events are triggered by the two push buttons(sw7, sw8, din0, din1)  
*/
uint8_t emaPlay_check_events( struct emaPlay_status *emaPlay )
{
    // locals vars
    static uint8_t ev[NUM_EMA_PLAY_EV];
    int diff;

    // get the latest state of events
    if ( sw0_pending ) {
        sw0_pending = 0;
        ev[EV_SW0_PRESS_RELEASE] = 1;
    }else{
        ev[EV_SW0_PRESS_RELEASE] = 0;
    }
    if ( sw1_pending ) {
        sw1_pending = 0;
        ev[EV_SW1_PRESS_RELEASE] = 1;
    }else{
        ev[EV_SW1_PRESS_RELEASE] = 0;
    }

    // check for a long press(which is not a reportable event, but rather a local trigger)
    if ( sw0_long_press ) {
        sw0_long_press = 0;
        return 2;
    }

    ev[EV_DIN0_STATE] = din0_state;
    ev[EV_DIN1_STATE] = din1_state;

    // compare to current emaPlay status
    diff = memcmp( emaPlay->events, ev, 4 );
    if ( diff ) {
        // update emaPlay status
        memcpy( emaPlay->events, ev, 4 );
		return 1;
    }else{
        return 0;
    }
}

/* 
timed_out: 
Checks against a passed in time to experiation value (time_to_err)  
*/
uint8_t timed_out( uint16_t timer, uint32_t time_to_err )
{
    uint8_t err = 0;

    if ( (timer * SYS_TASK_EXECUTION_INTERVAL) >= time_to_err ) {
        err = 1;
    }

    return err;
}

/* 
emaPlay_read_temp_humidiity: 
Communicates over the MCU i2c interface, to get the current temp and relative humidity from U1. The global emaPlay(*emaPlay) 
struct is updated. 
*/
void emaPlay_read_temp_humidiity( struct emaPlay_status *emaPlay )
{
	uint8_t tx_buff[2];
	uint8_t rx_buff[6];
    uint16_t dataRaw;
    float tempF, rHum;

    tx_buff[0] = 0x24;  // no clock stretching
    tx_buff[1] = 0x16;  // low repeatability
    i2c_m_sync_cmd_write( &I2C, 0x45, tx_buff, 2 );

    TIME_DELAY_ms( 5 );

    i2c_m_sync_cmd_read( &I2C, 0x45, rx_buff, 6 );

    // temp conversion formula
    // Tf = -49 + 315 x (St(dec) / 2^16 - 1), where St is bytes 0-msb, 1-lsb of rx_buff
    dataRaw = rx_buff[0] << 8;
    dataRaw |= rx_buff[1];
    tempF = -49 + 315 * ((float)dataRaw / 65535);

    // humidity conversion formula
    // Rh = 100 * ( Srh(dec) / 2^16 - 1) 
    dataRaw = rx_buff[3] << 8;
    dataRaw |= rx_buff[4];
    rHum = 100 * ( (float)dataRaw / 65535 );

    emaPlay->stats.temp = tempF - TEMP_OFFSET;
    emaPlay->stats.r_hum = rHum + HUMIDITY_OFFSET;
}

/* 
emaPlay_scan_analog_inputs: 
Reads the MCU analog intput channels and averages the values over time.The global emaPlay(*emaPlay) 
struct is updated. 
*/
void emaPlay_scan_analog_inputs( struct emaPlay_status *emaPlay )
{
    static uint8_t adc_cnt = 0;
    static uint16_t adc_val[MAX_NUM_ADC_INPUTS];
    uint16_t cur_val;
    uint8_t i, adc_pin;

    // average the adc values for each channel
    if ( adc_cnt < NUM_ADC_READS_TO_AVG ) {
        adc_cnt++;
        adc_pin = ADC_INPUTCTRL_MUXPOS_AIN12;
        for ( i=0; i<MAX_NUM_ADC_INPUTS; i++ ) {
            // set the pos & neg channels
            adc_sync_set_inputs( &ADC_0, adc_pin+i, 0x18/*gnd*/, 0 );
            // read the value
            adc_sync_read_channel( &ADC_0, 0, (uint8_t*)&cur_val, 2 );
            // add to last reading 
            adc_val[i] += cur_val;
        }
    }else{
        // avg the readings
        emaPlay->stats.light = adc_val[0] / NUM_ADC_READS_TO_AVG;
        emaPlay->stats.ema_v = adc_val[1] / NUM_ADC_READS_TO_AVG; 
        emaPlay->stats.sys_v =  adc_val[2] / NUM_ADC_READS_TO_AVG; 
        emaPlay->stats.user_adc = adc_val[3] / NUM_ADC_READS_TO_AVG;
        // reset vars
        for ( i=0; i<MAX_NUM_ADC_INPUTS; i++ ) {
            adc_val[i] = 0;
        }
        adc_cnt = 0;
    }
}

static char leaf_msg_data[512];
static const char event_json_names[NUM_EMA_PLAY_EV][EV_JSON_NAME_SIZE] = 
{
    {"sw1_state"},
    {"sw2_state"},
    {"din1_state"},
    {"din2_state"}
};

/* 
emaPlay_populate_leaf_event_data: 
Populates the pertinent leaf event data into the output buffer(*leaf_data_buff)
*/
uint16_t emaPlay_populate_leaf_event_data( char *leaf_data_buff, uint8_t *event_data )
{
    uint8_t i;
    int l;

    // grab the latest event data and build the leaf json msg from it
    l = sprintf( leaf_data_buff, "{\"type\": \"ema/Canopy Demo\",\"events\":{" );
    for ( i=0; i<NUM_EMA_PLAY_EV; i++ ) {
        l += sprintf( leaf_data_buff+l, "\"%s\": %d,", (char*)&event_json_names[i][0], (int)event_data[i] );
    }
    l += sprintf( leaf_data_buff+l-1/*overwrite trailing comma*/, "}}" );

    return (uint16_t)l;
}

/* 
emaPlay_convert_to_decimal: 
Converts non decimal values to decimal for JSON usage
*/
void emaPlay_convert_to_decimal( struct emaPlay_stats *stat_data )
{
    float sys_scaled_v, ema_scaled_v, sys_voltage, ema_voltage;
    uint16_t sys_v_l, sys_v_r, ema_v_l, ema_v_r, temp_l, temp_r, hum_l, hum_r, t1, u1;

    // convert adc values to relevant data

    // convert 12 bit adc value to decimal voltage value
    sys_scaled_v = (float)((3.3f / 4095) * stat_data->sys_v);
    ema_scaled_v = (float)((3.3f / 4095) * stat_data->ema_v);

    // convert/unscale to actual input voltage from schematic voltage divider values
    // eq->       (R1+R2) x  Vout           Sys V:                                          ema V:
    //      Vin = ---------------           R1(sch. R21) = 200K, R2(sch. R26) = 24K         R1(sch. R6) = 20K, R2(sch. R10) = 20K
    //                  R2
    sys_voltage = (float)(((200+24) * sys_scaled_v) / 24);
    // for the sys voltage, add back in the diode d3 voltage drop(.15 measured)
    sys_voltage += .15f;
    ema_voltage = (float)(((20+20) * ema_scaled_v) / 20);
    // get the left side of decimal point
    sys_v_l = (uint16_t)sys_voltage;
    ema_v_l = (uint16_t)ema_voltage;
    // get the right side of decimal point
    sys_voltage = sys_voltage * 10;
    sys_v_r = (uint16_t)sys_voltage;
    sys_v_r %= 10;
    ema_voltage = ema_voltage * 10;
    ema_v_r = (uint16_t)ema_voltage;
    ema_v_r %= 10;
    // do the same for temp and hum
    temp_l = (uint16_t)stat_data->temp;
    hum_l = (uint16_t)stat_data->r_hum;
    // get the right side of decimal point
    t1 = stat_data->temp * 10;
    temp_r = (uint16_t)t1;
    temp_r %= 10;
    u1 = stat_data->r_hum * 10;
    hum_r = (uint16_t)u1;
    hum_r %= 10;

    // update the emaPlay structure
    stat_data->t_l = temp_l;
    stat_data->t_r = temp_r;
    stat_data->h_l = hum_l;
    stat_data->h_r = hum_r;
    stat_data->eV_l = ema_v_l;
    stat_data->eV_r = ema_v_r;
    stat_data->sV_l = sys_v_l;
    stat_data->sV_r = sys_v_r;
}

#if OPT_PERIODIC_STATISTIC == 0
/* 
emaPlay_check_stat_delta: 
This function checks to see if a statistic value has changed by the set delta(emaPlay_config.h) value since the 
last time it was sent by the lead agent. 
*/
int emaPlay_check_stat_delta( struct emaPlay_stats *stat_data )
{
    #define _12BIT_ADC_COUNT    .0008f   // 3.3 / 4095
    #define EMA_V_TO_ADC_CNT    EMA_V_DELTA / _12BIT_ADC_COUNT
    #define SYS_V_TO_ADC_CNT    SYS_V_DELTA / _12BIT_ADC_COUNT
    static struct emaPlay_stats last_stats;
    int diff, delta;
    float tmp;

    delta = 0;

    // compare the old values to the new ones, to see if any changed
    diff = memcmp( stat_data, &last_stats, sizeof(struct emaPlay_stats) );
    if ( diff ) {
        // check against deltas 
        // 
        // temp
        tmp = stat_data->temp - last_stats.temp;
        if ( fabs( tmp ) >= TEMP_DELTA ) {
            delta = 1;
            //emaPlay_app_debug_send( "\r\ntemp Delta\r\n", sizeof("\r\ntemp Delta\r\n") );
        }
        // hum
        tmp = stat_data->r_hum - last_stats.r_hum;
        if ( fabs( tmp ) >= R_HUM_DELTA ) {
            delta = 1;
            //emaPlay_app_debug_send( "\r\nhum Delta\r\n", sizeof("\r\nhum Delta\r\n") );
        }
        // ema V
        tmp = stat_data->ema_v - last_stats.ema_v;
        if ( fabs( tmp ) >= EMA_V_TO_ADC_CNT ) {
            delta = 1;
            //emaPlay_app_debug_send( "\r\nema V Delta\r\n", sizeof("\r\nema V Delta\r\n") );
        }
        // sys V
        tmp = stat_data->sys_v - last_stats.sys_v;
        if ( fabs( tmp ) >= SYS_V_TO_ADC_CNT ) {
            delta = 1;
            //emaPlay_app_debug_send( "\r\nsys V Delta\r\n", sizeof("\r\nsys V Delta\r\n") );
        }

        tmp = stat_data->siq_q - last_stats.siq_q;
        if ( fabs( tmp ) >= SIG_Q_DELTA ) {
            delta = 1;
            //emaPlay_app_debug_send( "\r\nsig Q Delta\r\n", sizeof("\r\nsig Q Delta\r\n") );
        }

        // handle light different as its based on %
        if ( last_stats.light ) {
            tmp = stat_data->light - last_stats.light;
            tmp = fabs( tmp );
                tmp /= last_stats.light;
            tmp *= 100;
            if ( tmp >= LIGHT_DELTA ) {
                delta = 1;
                //emaPlay_app_debug_send( "\r\nlight Delta\r\n", sizeof("\r\nlight Delta\r\n") );
            }
        }

        // handle user adc different as its based on %
        if ( last_stats.user_adc ) {
            tmp = stat_data->user_adc - last_stats.user_adc;
            tmp = fabs( tmp );
                tmp /= last_stats.user_adc;
            tmp *= 100;
            if ( tmp >= USER_ADC_DELTA ) {
                delta = 1;
                //emaPlay_app_debug_send( "\r\nuser ADC Delta\r\n", sizeof("\r\nuser ADC Delta\r\n") );
            }
        }

        // only update the previous copy if a delta change occured
        if ( delta == 1 ) {
            memcpy(&last_stats, stat_data, sizeof(struct emaPlay_stats));
        }
    }

    return delta;
}
#endif

/* 
emaPlay_populate_leaf_stat_data: 
Populates the pertinent leaf statistic data into the output buffer(*leaf_data_buff)
*/
uint16_t emaPlay_populate_leaf_stat_data( char *leaf_data_buff, struct emaPlay_stats *stat_data )
{
    int l;

    // convert the floats and unit16_t to left and right decimal values
    emaPlay_convert_to_decimal( stat_data  );

    // grab the latest stat data and build the leaf json msg from it
    l = sprintf( leaf_data_buff, "{\"type\": \"ema/Canopy Demo\",\"statistics\":{" );
    l += sprintf( leaf_data_buff+l, " \"env_temp\": %d.%d,\"env_humidity\": %d.%d,\"env_light\": %d,\"sys_volts\": %d.%d,\"ema_volts\": %d.%d,\"sig_qual\": %d,\"user_adc\": %d",
                                       stat_data->t_l, stat_data->t_r, stat_data->h_l, stat_data->h_r, stat_data->light, stat_data->sV_l, stat_data->sV_r, stat_data->eV_l, stat_data->eV_r, stat_data->siq_q, stat_data->user_adc );
    l += sprintf( leaf_data_buff+l, "}}" );

    return (uint16_t)l;
}

/* 
emaPlay_system_task: 
This is the main task or state machine that controls the functionality of this demo project. It's purpose is to keep 
the cellular connection up and running as well as report telemetry data to Canopy via the lead agent.
*/
void emaPlay_system_task(struct emaLink_context *link_handle, 
                         struct modem_at_context *modem_handle, 
                         struct ema_status *ema, 
                         struct emaPlay_status *emaPlay,  
                         uint8_t *ev )
{
    static uint8_t system_state = SYS_ST_IDLE;
    static uint8_t pwr_st, last_pwr_st, run_st;
    static uint8_t pwr_up = 1;
    static uint8_t err_code;
    static uint8_t recover_cnt = 0;
    static uint8_t i = 0;
    static uint8_t msg = 0;
    uint8_t urc_st = 0;
    int at_st = 0;
    int st = 0;
    int http_res_code;
    static uint16_t delay = 0;          
    static uint16_t error_timer = 0;
    static uint32_t exp_urcs = 0;
    static uint8_t event_data[NUM_EMA_PLAY_EV];
    static struct emaPlay_stats stat_data;
    static char notification[EMA_BOARD_NOTIFY_MSG_SIZE];
    static uint32_t network_check_timer;  
        #define  SYS_NETWORK_CHECK_TIMER    20000  // 20 seconds
    static uint32_t reboot_timer;
        #define  SYS_REBOOT_TIME            50000  // 50 seconds
    static uint8_t reboot_pending = 0;
    static enum LeafMessageTypes leaf_msg_type;
    static int leaf_msg_len = 0;
    static int leaf_retry = LEAF_ATTEMPTS;
    static char leaf_cmd[LEAF_CMD_SIZE];

    #if OPT_PERIODIC_STATISTIC
    static uint32_t stat_msg_timer;
        #define  SYS_STAT_MSG_INTERVAL      30000  // 30 seconds
    #else
    static uint32_t delta_check_timer;
        #define  SYS_DELTA_CHECK_INTERVAL   1000  // 1 seconds
    #endif

    #if ENABLE_LEAF_NOOP
    static uint32_t noop_msg_timer;
        #define  SYS_NOOP_MSG_TIMER         10000   // 5 seconds
    #endif

    // used for debug output
    char str[128];
    int l;

    // check for power
    pwr_st = emaPlay_check_power( &emaPlay->stats );

    // process only if pwr_st changes
    if ( (pwr_st != last_pwr_st) || pwr_up ) {
        pwr_up = 0;
        last_pwr_st = pwr_st;
        if (pwr_st == 1) {
            // power good
            l = sprintf(str, "\r\n%d mS: ema power detected", (int)u32Sys1msTic );
            emaPlay_app_debug_send( str, l );
            emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF );
            delay = 1;
        }else{
            // no power 
            l = sprintf(str, "\r\n%d mS: No ema power, check J9, ExtVin, or U6(ema load switch), waiting...", (int)u32Sys1msTic );
            emaPlay_app_debug_send( str, l );
            emaPlay_set_led_array( LED_PATTERN_PAIR_ERROR, 
                                   LED_PATTERN_PAIR_ERROR,
                                   LED_PATTERN_PAIR_ERROR,
                                   LED_PATTERN_PAIR_ERROR );
            // reset system
            emaPlay_init( emaPlay );
            link_handle->urc_status = 0;
            system_state = SYS_ST_IDLE;
            run_st = 0;
            msg = 0;
        }
    }

    if ( pwr_st == 1 ) {

        // check for start/stop trigger(sw7 long press)
        if ( *ev == 2 ) {
            *ev = 0;
            // tigger to start stop, toggle run_st
            run_st ^= 1;
            if ( run_st == 1 ) {
                // (re)start/init ema
                l = sprintf(str, "\r\n%d mS: Initializing ema, applying power. Waiting.", (int)u32Sys1msTic );
                emaPlay_app_debug_send( str, l );
                emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                       LED_PATTERN_PAIR_OFF,
                                       LED_PATTERN_PAIR_OFF,
                                       LED_PATTERN_PAIR_OFF );
                // reset system
                emaPlay_init( emaPlay );
                link_handle->urc_status = 0;
                system_state = SYS_ST_INIT_EMA_APPLY_POWER;
            }else{
                // gracefully shutdown ema
                emaPlay_set_led_array( LED_PATTERN_PAIR_OFF, 
                                       LED_PATTERN_PAIR_OFF,
                                       LED_PATTERN_PAIR_OFF,
                                       LED_PATTERN_PAIR_OFF );
                l = sprintf(str, "\r\n%d mS: Gracefully shutting down ema", (int)u32Sys1msTic );
                emaPlay_app_debug_send( str, l );
                system_state = SYS_ST_TURN_OFF_MODULE;
                emaPlay->pending_cmd = SW7_SHUTDOWN_EMA; 
                msg = 0;
            }
        }

        if (delay) {
            delay--;
        }else{
            switch (system_state) {
            case SYS_ST_IDLE:
                // scroll the leds indicating idle state
                set_led_blink_pattern( i++, 200, 500 );
                if ( i == 4 ) {
                    i = 0;
                }
                delay = 1;

                // spit out a msg to the user
                if ( msg == 0 ) {
                    msg = 1;
                    l = sprintf(str, "\r\n%d mS: Press and hold SW7 for 2 seconds to start\r\n", (int)u32Sys1msTic);
                    emaPlay_app_debug_send( str, l );
                }

                break;
            case SYS_ST_NORMAL_OP:
                // normal operation priorities:
                // 1. check for events occured
                //      a. Asynchronous admin/urc task detection
                //      b. dynamic emaPLay/leaf events
                //      c. Periodic leaf stat msg
                //      d. Periodic Leaf noop msg to get cmds(if used)
                //      e. Poll network status
                // 2. check network status

                // a. check for any admin task and/or urcs
                exp_urcs = EMA_LINK_CNTX_ADMIN_ASYNCH_GROUP;
                urc_st = emaPlay_checkURC_any( link_handle, exp_urcs );
                if ( urc_st ) {
                    // process asynch URCs and/or mgmt tasks
                    system_state = SYS_ST_ASYNCH_TASK;
                    break;
                }

                // check the reboot timer to see if we need to start cleaning up
                if ( reboot_pending == 1 ) {
                    if (TIME_ELAPSED_ms(reboot_timer) >= SYS_REBOOT_TIME) {
                        reboot_pending = 0;
                        // deinit, and go handle the reboot
                        l = sprintf(str, "\r\n%d mS: Prepping for ema reboot", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        emaModemAT_DeactivatePDPCntx( modem_handle, ema->cntx_id );
                        emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF );
                        emaPlay->leaf_init = 0;
                        system_state = SYS_ST_HANDLE_REBOOT;
                        break;
                    }
                }

                // b. handle an event, that needs to be sent using leaf
                if ( *ev ) {
                    // clear the event
                    l = sprintf(str, "\r\n%d mS: Event detected", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );

                    *ev = 0;

                    // snapshot the event data and build the leaf msg
                    memcpy( event_data, emaPlay->events, NUM_EMA_PLAY_EV );
                    leaf_msg_type = LEAF_EVENT;
                    system_state = SYS_ST_CHECK_SESSION;
                    break;
                }

                #if OPT_PERIODIC_STATISTIC
                // c. periodic stat msg(if enabled)
                if ( TIME_ELAPSED_ms(stat_msg_timer) >= SYS_STAT_MSG_INTERVAL ) {
                    l = sprintf(str, "\r\n%d mS: Periodic Statistic message", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    stat_msg_timer = TIME_ELAPSED_ms( 0 );
                    // grab the current sig q
                    emaPlay->stats.siq_q = ema->calc_sig_q;
                    // snapshot the stat data and build the leaf msg
                    memcpy( &stat_data, &emaPlay->stats, sizeof(struct emaPlay_stats) );
                    leaf_msg_type = LEAF_STATISTIC;
                    system_state = SYS_ST_CHECK_SESSION;
                    break;
                }
                #else
                // delta mode sends immediately(< +/-SYS_DELTA_CHECK_INTERVAL) if delta change detected in stat values
                //
                // in order to detect a delta change we must periodically snapshot the current values and compare them against new ones
                if ( TIME_ELAPSED_ms(delta_check_timer) >= SYS_DELTA_CHECK_INTERVAL ) {
                    delta_check_timer = TIME_ELAPSED_ms( 0 );
                    // grab the current sig q, before checking
                    emaPlay->stats.siq_q = ema->calc_sig_q;
                    st = emaPlay_check_stat_delta( &emaPlay->stats );
                    if ( st == 1 ) {
                        l = sprintf(str, "\r\n%d mS: Delta change detected", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        // we have a delta change, build and send the msg
                        memcpy( &stat_data, &emaPlay->stats, sizeof(struct emaPlay_stats) );
                        leaf_msg_type = LEAF_STATISTIC;
                        system_state = SYS_ST_CHECK_SESSION;
                        break;
                    }
                }

                #endif

                #if ENABLE_LEAF_NOOP
                // d. periodic noop msg
                if ( TIME_ELAPSED_ms(noop_msg_timer) >= SYS_NOOP_MSG_TIMER) {
                    //noop_msg_timer = TIME_ELAPSED_ms( 0 );
                    leaf_msg_type = LEAF_NOOP;
                    system_state = SYS_ST_CHECK_SESSION;
                    break;
                }
                #endif

                // e. periodic network status check
                if ( TIME_ELAPSED_ms(network_check_timer) >= SYS_NETWORK_CHECK_TIMER ) {
                    network_check_timer = TIME_ELAPSED_ms( 0 );
                    // update the network status
                    l = sprintf(str, "\r\n%d mS: Periodic network status check", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_CHECK_NETWORK;
                    error_timer = 0;
                    break;
                }

            case SYS_ST_ASYNCH_TASK:
                // reserved for when ema is autonomously doing an mgmt/monitoring task

                // process URCs to see whats going on
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_MODULE_POWERING_OFF );
                if ( urc_st ) {
                    // ema is powering off the cell module
                    l = sprintf(str, "\r\n%d mS: ema cycling module power, waiting", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    // ack/clear the urc flag and wait for ema to turn the module back on
                    // link_handle->urc_status &= ~EMA_LINK_CNTX_MODULE_POWERING_OFF;
                    system_state = SYS_ST_INIT_EMA_TURN_ON_MODULE;
                    error_timer = 0;
                    emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF );
                    // update tracking info
                    emaPlay->auto_module_reboots++;
                }
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_EMA_REBOOTING_60_SECONDS );
                if ( urc_st ) {
                    // ema will be rebooting in 60 seconds, time to prep for it
                    l = sprintf(str, "\r\n%d mS: ema rebooting in 60 seconds", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    // ack/clear the urc, start timing the 60 seconds, normal op can resume up until then, we'll wait 50 seconds
                    reboot_timer = TIME_ELAPSED_ms( 0 );
                    reboot_pending = 1;
                    link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_REBOOTING_60_SECONDS;
                    system_state = SYS_ST_NORMAL_OP;
                }
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_EMA_MICROFOTA_STARTED );
                if ( urc_st ) {
                    // microFOTA(ema FW update over the air) download has started
                    l = sprintf(str, "\r\n%d mS: microFOTA download started", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    // ack/clear the urc flag and continue normal ops
                    link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_STARTED;
                    system_state = SYS_ST_NORMAL_OP;
                }
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_EMA_MICROFOTA_COMPLETE );
                if ( urc_st ) {
                    // microFOTA(ema FW update over the air) download has completed, a reboot is inevitable
                    l = sprintf(str, "\r\n%d mS: microFOTA download complete", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    // ack/clear the urc flag and continue normal ops, a reboot is now pending
                    link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_COMPLETE;
                    system_state = SYS_ST_NORMAL_OP;
                }
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_EMA_MICROFOTA_FAILED );
                if ( urc_st ) {
                    // microFOTA(ema FW update over the air) download has failed and will resume later
                    l = sprintf(str, "\r\n%d mS: microFOTA download failed, resuming later", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    // ack/clear the urc flag and continue normal ops, 
                    link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_MICROFOTA_FAILED;
                    system_state = SYS_ST_NORMAL_OP;
                }
                urc_st = emaPlay_checkURC_mask( link_handle, EMA_LINK_CNTX_EMA_BOARD_NOTIFY );
                if ( urc_st ) {
                    // ema has data from the server
                    l = sprintf(str, "\r\n%d mS: Board Notification: %s", (int)u32Sys1msTic, link_handle->bn_msg );
                    emaPlay_app_debug_send( str, l );
                    // clear/ack the flag and process
                    link_handle->urc_status &= ~EMA_LINK_CNTX_EMA_BOARD_NOTIFY;
                    // copy the msg locally
                    strcpy( notification, link_handle->bn_msg );
                    emaPlay_process_board_notify( notification, &system_state, emaPlay );
                    if ( emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA ) {
                        // go process the cmd
                        l = sprintf(str, "\r\n%d mS: Rebooting ema", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_TURN_OFF_MODULE;
                        error_timer = 0;
                        emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF );
                        // update tracking info
                        emaPlay->man_ema_reboots++;
                    } else if (emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA_PLAY ) {
                        // go process the cmd
                        l = sprintf(str, "\r\n%d mS: Rebooting ema:Play", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_TURN_OFF_MODULE;
                        error_timer = 0;
                        emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF,
                                               LED_PATTERN_PAIR_OFF );
                    }else{
                        system_state = SYS_ST_NORMAL_OP;
                    }
                }

                break;
            case SYS_ST_HANDLE_REBOOT:
                // handle an asynch reboot, typically occurs after a microFOTA
            
                exp_urcs = EMA_LINK_CNTX_EMA_REBOOTING_NOW | EMA_LINK_CNTX_EMA_AT_READY | EMA_LINK_CNTX_MODULE_READY | EMA_LINK_CNTX_MODULE_UART_READY;
                urc_st = emaPlay_checkURC_mask( link_handle, exp_urcs );
                if ( urc_st ) {
                    // reboot is complete, check comms
                    at_st = emaLinkAT_CheckComm( link_handle );
                    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
                        // ema comms good
                        emaPlay_init_ema_cmds( link_handle, ema, emaPlay );
                        l = sprintf(str, "\r\n%d mS: ema Board ID set: %s", (int)u32Sys1msTic, ema->board_id );
                        emaPlay_app_debug_send( str, l );
                        at_st = emaModemAT_CheckComm( modem_handle );
                        if ( at_st == MOD_AT_ST_SUCCESS ) {
                            l = sprintf(str, "\r\n%d mS: ema reboot complete", (int)u32Sys1msTic );
                            emaPlay_app_debug_send( str, l );
                            emaModemAT_DisableEcho( modem_handle );
                            // successful reboot, (re)init everything
                            system_state = SYS_ST_CHECK_NETWORK;
                            error_timer = 0;
                        }else{
                            // error
                            system_state = SYS_ST_INIT_ERROR;
                            err_code = ERR_CODE_EMA_MODULE_RESPONSE;
                        }
                    }else{
                        // error
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_AT_RESPONSE;
                    }
                }else{
                    if ( timed_out( error_timer, EMA_REBOOT_WAIT_TIME ) ) {
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_URC_TIME_OUT;
                        break;
                    }
                    // delay =500 msec then check again
                    delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                    // track how long its taking to get the urc
                    error_timer += delay;
                }

                break;
            case SYS_ST_INIT_EMA_APPLY_POWER:
                // apply power to ema, module is still off

                l = sprintf(str, "." );
                emaPlay_app_debug_send( str, l );

                emaPlay_ema_power_control( EMA_POWER_ST_ON );
                // wait for the power on URC
                exp_urcs = EMA_LINK_CNTX_EMA_AT_READY;
                urc_st = emaPlay_checkURC_mask( link_handle, exp_urcs );
                if ( urc_st ) {
                    // check ema AT comms
                    at_st = emaLinkAT_CheckComm( link_handle );
                    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
                        // comms good, disable echo, get the ema sn and fw ver
                        emaPlay_init_ema_cmds( link_handle, ema, emaPlay );
                        l = sprintf(str, "\r\n%d mS: ema Board ID set: %s", (int)u32Sys1msTic, ema->board_id );
                        emaPlay_app_debug_send( str, l );
                        // move to next state
                        l = sprintf(str, "\r\n%d mS: ema Powered ON. Turning module ON. Waiting.", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_INIT_EMA_TURN_ON_MODULE;
                        error_timer = 0;
                    }else{
                        // error
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_AT_RESPONSE;
                    }
                }else{
                    if ( timed_out( error_timer, EMA_APPLY_POWER_WAIT_TIME ) ) {
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_URC_TIME_OUT;
                        break;
                    }
                    // delay 500ms sec then check again
                    delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                    // track how long its taking to get the urc
                    error_timer += delay;
                }

                break;
            case SYS_ST_INIT_EMA_TURN_ON_MODULE:
                // ema has power, turn module on

                l = sprintf(str, "." );
                emaPlay_app_debug_send( str, l );

                emaPlay_ema_on_off_control( EMA_ON_OFF_ST_ON );
                // wait for the module and interface ready URCs
                exp_urcs = /*EMA_LINK_CNTX_MODULE_POWERING_ON |*/ EMA_LINK_CNTX_MODULE_READY | EMA_LINK_CNTX_MODULE_UART_READY;
                urc_st = emaPlay_checkURC_mask( link_handle, exp_urcs );
                if ( urc_st ) {
                    // on-board module is ready, check comms
                    at_st = emaModemAT_CheckComm( modem_handle );
                    if ( at_st == MOD_AT_ST_SUCCESS ) {
                        // comms good, disable echo 
                        l = sprintf(str, "\r\n%d mS: Module ON and ready", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        l = sprintf(str, "\r\n%d mS: Checking network status, searching...", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        emaModemAT_DisableEcho( modem_handle );
                        system_state = SYS_ST_CHECK_NETWORK;
                        error_timer = 0;
                    }else{
                        // error
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_MODULE_RESPONSE;
                    }
                }else{
                    if ( timed_out( error_timer, EMA_MODULE_READY_WAIT_TIME ) ) {
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_EMA_URC_TIME_OUT;
                        break;
                    }
                    // delay =500 msec then check again
                    delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                    // track how long its taking to get the urc
                    error_timer += delay;
                }

                break;
            case SYS_ST_CHECK_NETWORK:
                // checks emas network status, before moving on
                at_st = (uint8_t)emaPlay_glimpse( link_handle, ema );
                if ( at_st == 1 ) {
                    if ( emaPlay->leaf_init == 0 ) {
                        l = sprintf(str, "\r\n%d mS: Registered on the %s network", (int)u32Sys1msTic, ema->act_carrier );
                        emaPlay_app_debug_send( str, l );
                        l = sprintf(str, "\r\n%d mS: Checking data session", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_CHECK_SESSION;
                    }else{
                        system_state = SYS_ST_NORMAL_OP;
                    }
                    error_timer = 0;
                }else{
                    if ( timed_out( error_timer, EMA_NETWORK_READY_TIME ) ) {
                        system_state = SYS_ST_INIT_ERROR;
                        err_code = ERR_CODE_NETWORK_ERROR;
                        break;
                    }
                    // delay 5 sec then check again
                    delay = 5000 / SYS_TASK_EXECUTION_INTERVAL;
                    // track how long its taking to get the urc
                    error_timer += delay;
                }

                break;
            case SYS_ST_CHECK_SESSION:
                // checks and (re)activates the data/mobile ip session

                // check for->1. context activation, 2. Valid mobile IP
                at_st = emaModemAT_StatusPDPCntx( modem_handle, ema->cntx_id, &ema->cntx_st );
                if ( at_st == MOD_AT_ST_SUCCESS ) {
                    if ( ema->cntx_st == 1 ) {
                        at_st = emaModemAT_GetMobileIP( modem_handle, ema->cntx_id, ema->IP );
                        if ( at_st == MOD_AT_ST_SUCCESS ) {
                            st = emaPlay_confirm_mobileIP( ema->IP );
                            if ( st == 1 ) {
                                // data session active, either init leaf or go send the leaf msg
                                if ( emaPlay->leaf_init == 0 ) {
                                    l = sprintf(str, "\r\n%d mS: Data session active", (int)u32Sys1msTic );
                                    emaPlay_app_debug_send( str, l );
                                    system_state = SYS_ST_INIT_LEAF;
                                }else{
                                    system_state = SYS_ST_BUILD_LEAF_MSG;
                                }
                                break;
                            }else{
                                // (re)start the data session, falls through to next state, sys_state remains the same

                            }
                        }
                    }else{
                        // (re)start the data session, falls through to next state, sys_state remains the same

                    }
                }

            case SYS_ST_START_SESSION:
                // activate the data/mobile IP session
                
                l = sprintf(str, "\r\n%d mS: No session, starting session", (int)u32Sys1msTic );
                emaPlay_app_debug_send( str, l );

                at_st = emaModemAT_ActivatePDPCntx( modem_handle, ema->cntx_id );
                if ( at_st != MOD_AT_ST_SUCCESS ) {
                    system_state = SYS_ST_INIT_ERROR;
                    err_code = ERR_CODE_EMA_MODULE_RESPONSE;
                }

                break;
            case SYS_ST_INIT_LEAF:
                // init leaf agent

                l = sprintf(str, "\r\n%d mS: Starting Leaf", (int)u32Sys1msTic );
                emaPlay_app_debug_send( str, l );

                #if OPT_PERIODIC_STATISTIC
                stat_msg_timer = TIME_ELAPSED_ms( 0 );
                #endif
                network_check_timer = TIME_ELAPSED_ms( 0 );
                #if ENABLE_LEAF_NOOP
                noop_msg_timer = TIME_ELAPSED_ms( 0 );
                #endif

                st = start_leaf( modem_handle, ema->cntx_id, ema->sn, "leaf.gocanopy.io", "banyan", "leaf.optconnect.prod", "SkpZWxbVaBpr6WGq!" );
                if ( st == 1 ) {
                    // the leaf agent on ema:Play is now ready, continue to normal operstion
                    l = sprintf(str, "\r\n%d mS: Leaf Started", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    emaPlay->leaf_init = 1;
                    system_state = SYS_ST_NORMAL_OP;
                }else{
                    system_state = SYS_ST_INIT_ERROR;
                    err_code = ERR_CODE_LEAF_INIT;
                }

                break;
            case SYS_ST_BUILD_LEAF_MSG:
                // builds a leaf msg
                
                // grab the relevant ema:Play data
                if ( leaf_msg_type == LEAF_EVENT ) {
                    l = sprintf(str, "\r\n%d mS: Building Leaf Event message", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    emaPlay_populate_leaf_event_data( leaf_msg_data, event_data );
                } else if ( leaf_msg_type == LEAF_STATISTIC ) {
                    l = sprintf(str, "\r\n%d mS: Building Leaf Statistic message", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    emaPlay_populate_leaf_stat_data( leaf_msg_data, &stat_data );
                    // copy back the calc left and right decimal values
                    memcpy( &emaPlay->stats, &stat_data, sizeof(struct emaPlay_stats) );
                } 

                if ( leaf_msg_type == LEAF_NOOP ) {
                    l = sprintf(str, "\r\n%d mS: Building Leaf Noop message", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    leaf_msg_len = build_leaf_noop( );
                }else{
                    leaf_msg_len = build_leaf_message(leaf_msg_type, "ema:Play", leaf_msg_data);
                }

                // send the msg
                system_state = SYS_ST_SEND_LEAF_MSG;

                break;
            case SYS_ST_SEND_LEAF_MSG:
                // send the leaf msg to canopy

                // send the leaf event/stat msg with retries
                if ( leaf_retry ) {
                    l = sprintf(str, "\r\n%d mS: Sending Leaf message", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    st = send_leaf_query(modem_handle, ema->cntx_id, leaf_msg_len, &http_res_code, leaf_cmd);
                    if ( st == 0 ) {
                        // leaf msg fail, remain in this state
                        l = sprintf(str, "\r\n%d mS: Leaf query error, http code: %d", (int)u32Sys1msTic, http_res_code );
                        emaPlay_app_debug_send( str, l );
                        leaf_retry--;
                        break;
                    }else{
                        // leaf msg success
                        l = sprintf(str, "\r\n%d mS: Leaf query success", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        #if ENABLE_LEAF_NOOP
                        noop_msg_timer = TIME_ELAPSED_ms( 0 );
                        #endif
                        if ( st != 2 ) {
                            system_state = SYS_ST_NORMAL_OP;
                            break;
                        }
                        // fall through for cmd
                    }
                }else{
                    // exhausted retries
                    leaf_retry = LEAF_ATTEMPTS;
                    system_state = SYS_ST_NORMAL_OP;
                    break;
                }

                // falls through for leaf cmd data
            case SYS_ST_PROCESS_LEAF_CMD:
                // handle a leaf cmd coming from canopy
                
                emaPlay_process_leaf_cmd( leaf_cmd, emaPlay );
                if ( emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA ) {
                    // go process the cmd
                    l = sprintf(str, "\r\n%d mS: Rebooting ema", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_TURN_OFF_MODULE;
                    error_timer = 0;
                    emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF );
                    // update tracking info
                    emaPlay->man_ema_reboots++;
                } else if (emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA_PLAY ) {
                    // go process the cmd
                    l = sprintf(str, "\r\n%d mS: Rebooting ema:Play", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_TURN_OFF_MODULE;
                    error_timer = 0;
                    emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF,
                                           LED_PATTERN_PAIR_OFF );
                }else{
                    system_state = SYS_ST_NORMAL_OP;
                }

                break;
            case SYS_ST_RECOVER_EMA:
                // handle recovering ema

                l = sprintf(str, "\r\n%d mS: Starting recovery process...", (int)u32Sys1msTic );
                emaPlay_app_debug_send( str, l );

                // determine what is not responding
                at_st = emaLinkAT_CheckComm( link_handle );
                if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
                    // ema responded, check module
                    at_st = emaModemAT_CheckComm( modem_handle );
                    if ( at_st == MOD_AT_ST_SUCCESS ) {
                        // everything seems ok, go back to normal ops
                        l = sprintf(str, "\r\n%d mS: ema and Module communicating, resuming normal op", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        // if we get here more then 3 times, something is wrong, reboot ema entirely
                        if ( recover_cnt == 3 ) {
                            l = sprintf(str, "\r\n%d mS: Reached 3 recovery attempts, rebooting ema", (int)u32Sys1msTic );
                            emaPlay_app_debug_send( str, l );
                            recover_cnt = 0;
                            system_state = SYS_ST_EMA_REMOVE_POWER;
                            emaPlay->auto_module_reboots++;
                        }else{
                            recover_cnt++;
                            system_state = SYS_ST_NORMAL_OP;
                        }
                    }else{
                        // module did not respond, start the recovery process
                        l = sprintf(str, "\r\n%d mS: Module not responding, turning module OFF, waiting", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_TURN_OFF_MODULE;
                        error_timer = 0;
                    }
                }else{
                    // ema did not respond, start the power cycle process
                    l = sprintf(str, "\r\n%d mS: ema not responding, removing power", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_EMA_REMOVE_POWER;
                }

                break;
            case SYS_ST_EMA_REMOVE_POWER:
                // power down ema
                
                emaPlay_ema_power_control( EMA_POWER_ST_OFF );
                // since we have removed power, clear all ema URCs
                link_handle->urc_status = 0;

                if ( emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA_PLAY ) {
                    // go to an idle state and restart this application 
                    l = sprintf(str, "\r\n%d mS: Power removed, rebooting emaPlay\r\n\r\n", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_REBOOT_EMA_PLAY;
                    delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                } else if ( emaPlay->pending_cmd == SW7_SHUTDOWN_EMA ) {
                    // go idle
                    l = sprintf(str, "\r\n%d mS: Power removed, going idle", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_IDLE;
                } else {
                    l = sprintf(str, "\r\n%d mS: Power removed, reapplying power. Waiting.", (int)u32Sys1msTic );
                    emaPlay_app_debug_send( str, l );
                    system_state = SYS_ST_INIT_EMA_APPLY_POWER;
                    error_timer = 0;
                    // delay 2 sec then start over
                    delay = 2000 / SYS_TASK_EXECUTION_INTERVAL;
                }

                // clear any pending cmds
                if ( emaPlay->pending_cmd ) {
                    emaPlay->pending_cmd = 0;
                }

                break;
            case SYS_ST_TURN_OFF_MODULE:
                // turn module off

                l = sprintf(str, "." );
                emaPlay_app_debug_send( str, l );

                emaPlay_ema_on_off_control( EMA_ON_OFF_ST_OFF );
                // wait for the module off URCs
                exp_urcs = EMA_LINK_CNTX_MODULE_POWERING_OFF | EMA_LINK_CNTX_MODULE_POWERED_OFF | EMA_LINK_CNTX_MODULE_UART_DOWN;
                urc_st = emaPlay_checkURC_mask( link_handle, exp_urcs );
                if ( urc_st ) {
                    // module has gracefully turned off
                    // 
                    // is this a cmd to process?
                    if ( emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA || emaPlay->pending_cmd == BN_PENDING_CMD_REBOOT_EMA_PLAY ||
                         emaPlay->pending_cmd == SW7_SHUTDOWN_EMA ) {
                        // yes, a reboot cmd so go turn off ema power
                        l = sprintf(str, "\r\n%d mS: Module turned OFF, removing power", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                        system_state = SYS_ST_EMA_REMOVE_POWER;
                        error_timer = 0;
                    }else{
                        // no, delay, then turn module back on
                        l = sprintf(str, "\r\n%d mS: Module turned OFF, turning module ON, waiting", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        delay = 5000 / SYS_TASK_EXECUTION_INTERVAL;
                        system_state = SYS_ST_INIT_EMA_TURN_ON_MODULE;
                        error_timer = 0;
                    }
                }else{
                    if ( timed_out( error_timer, EMA_MODULE_OFF_WAIT_TIME ) ) {
                        // module didn't gracefully turn off, cut power
                        l = sprintf(str, "\r\n%d mS: Failed to turn module OFF", (int)u32Sys1msTic );
                        emaPlay_app_debug_send( str, l );
                        system_state = SYS_ST_EMA_REMOVE_POWER;
                        break;
                    }
                    // delay =500 msec then check again
                    delay = 500 / SYS_TASK_EXECUTION_INTERVAL;
                    // track how long its taking to get the urc
                    error_timer += delay;
                }

                break;
            case SYS_ST_INIT_ERROR:
                // set hw for error condition

                emaPlay_set_led_array( LED_PATTERN_PAIR_ERROR, 
                                       LED_PATTERN_PAIR_ERROR,
                                       LED_PATTERN_PAIR_ERROR,
                                       LED_PATTERN_PAIR_ERROR );

                system_state = SYS_ST_ERROR;

                // fall through
            case SYS_ST_ERROR:
                // let the user know an error occured with the leds

                // process error code
                switch (err_code) {
                case ERR_CODE_EMA_URC_TIME_OUT:
                    l = sprintf(str, "\r\n%d mS: Err# %d: URC(s) Not Received in time. URC Mask expected = 0x%.8X\r\n", (int)u32Sys1msTic, err_code, (int)exp_urcs);
                    emaPlay_app_debug_send( str, l );
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_EMA_AT_RESPONSE:
                    l = sprintf( str, "\r\n%d mS: Err# %d: No ema AT response\r\n", (int)u32Sys1msTic, err_code );
                    emaPlay_app_debug_send( str, l );
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_EMA_MODULE_RESPONSE:
                    l = sprintf( str, "\r\n%d mS: Err# %d: No module AT response\r\n", (int)u32Sys1msTic, err_code );
                    emaPlay_app_debug_send( str, l ); 
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_LEAF_RESPONSE:
                    l = sprintf( str, "\r\n%d mS: Err# %d: Leaf server error\r\n", (int)u32Sys1msTic, err_code );
                    emaPlay_app_debug_send( str, l );
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_LEAF_INIT:
                    l = sprintf( str, "\r\n%d mS: Err# %d: Leaf init error\r\n", (int)u32Sys1msTic, err_code );
                    emaPlay_app_debug_send( str, l );
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_NETWORK_ERROR:
                    l = sprintf( str, "\r\n%d mS: Err# %d: Carrier network error", (int)u32Sys1msTic, err_code );
                    emaPlay_app_debug_send( str, l );
                    err_code = ERR_CODE_PROCESSED;
                    break;
                case ERR_CODE_PROCESSED:
                    #if OPT_RECOVER_EMA
                    system_state = SYS_ST_RECOVER_EMA;
                    #endif
                    break;
                default:
                    break;
                }

                break;
            case SYS_ST_REBOOT_EMA_PLAY:
                // a reboot of emaPlay has been requested

               NVIC_SystemReset();

                break;
            default:
                break;
            }
        }
    }

}

/* 
emaPlay_process_board_notify: 
This function will process Board Noitfy commands that are preprogrammed and follow the following rules: 
 
Cmd/Control	        States	        Command	        Parameters (idx, duty(ms), period(ms))
LED 1	            Duty On/period	“control”	"4","duty(0-65535)",“period(0-65535)”
LED 2	            Duty On/period	“control”	"5","duty(0-65535)",“period(0-65535)”
Digital Output 1	Duty On/period	“control”	"6","duty(0-65535)",“period(0-65535)”
Digital Output 2	Duty On/period	“control”	"7","duty(0-65535)",“period(0-65535)”
Reboot ema		                    “reboot_ema”	
Reboot ema:Play		                “reboot_ema_play”	
*/
void emaPlay_process_board_notify( char *msg, uint8_t *sys_state, struct emaPlay_status *emaPlay )
{
    char *p;
    uint8_t param1;
    uint16_t param2, param3;

    // process this msg according to the user application

    // fomrat of msg data:
    // cmd,[output idx],[duty cycle ms],[period ms],[[output idx],[duty cycle ms],[period ms]], repeat, repeat

    // parse out the cmd
    if ( strstr( msg, BN_CMD_CONTROL_STR ) ) {
        // parse the remaining params
        p = strchr( msg, ',' );
        param1 = atoi( p+1 );
        p = strchr( p+1, ',' );
        param2 = atoi( p+1 );
        p = strchr( p+1, ',' );
        param3 = atoi( p+1 );
        // update the control output
        set_user_dout_pattern( param1, param2, param3 );
        // update the emaplay struct
        if ( param1 >= 4 ) {
            emaPlay->user_douts[param1 - 4].duty = param2;
            emaPlay->user_douts[param1 - 4].period = param3;
        }

        // are there multiple cmds?
        p = strchr( p+1, ',' );
        if ( p ) {
            param1 = atoi(p + 1);
            p = strchr( p+1, ',' );
            param2 = atoi( p+1 );
            p = strchr( p+1, ',' );
            param3 = atoi( p+1 );
            // update the control output
            set_user_dout_pattern( param1, param2, param3 );
            if ( param1 >= 4 ) {
                emaPlay->user_douts[param1 - 4].duty = param2;
                emaPlay->user_douts[param1 - 4].period = param3;
            }
            p = strchr( p+1, ',' );
            if ( p ) {
                param1 = atoi(p + 1);
                p = strchr( p+1, ',' );
                param2 = atoi( p+1 );
                p = strchr( p+1, ',' );
                param3 = atoi( p+1 );
                // update the control output
                set_user_dout_pattern( param1, param2, param3 );
                if ( param1 >= 4 ) {
                    emaPlay->user_douts[param1 - 4].duty = param2;
                    emaPlay->user_douts[param1 - 4].period = param3;
                }
                p = strchr( p+1, ',' );
                if ( p ) {
                    param1 = atoi(p + 1);
                    p = strchr( p+1, ',' );
                    param2 = atoi( p+1 );
                    p = strchr( p+1, ',' );
                    param3 = atoi( p+1 );
                    // update the control output
                    set_user_dout_pattern( param1, param2, param3 );
                    if ( param1 >= 4 ) {
                        emaPlay->user_douts[param1 - 4].duty = param2;
                        emaPlay->user_douts[param1 - 4].period = param3;
                    }
                }
            }
        }
    }
    else if ( strstr( msg, BN_CMD_REBOOT_EMA_PLAY_STR ) ) {
        // set this cmd as pending, the state machine will handle it
        emaPlay->pending_cmd = BN_PENDING_CMD_REBOOT_EMA_PLAY;
    }
    else if ( strstr( msg, BN_CMD_REBOOT_EMA_STR ) ) {
        // set this cmd as pending, the state machine will handle it
        emaPlay->pending_cmd = BN_PENDING_CMD_REBOOT_EMA;
    }
}

/* 
emaPlay_process_leaf_cmd: 
This function will process leaf commands that are preprogrammed and follow the following rules: 
 
"command":"[cmd]","params":"[\"[output idx]\", [duty cycle ms], [period ms]]"	
*/
void emaPlay_process_leaf_cmd( char *cmd, struct emaPlay_status *emaPlay )
{
    char *p;
    uint8_t param1;
    uint16_t param2, param3;

    // process this msg according to the user application

    // fomrat of leaf cmd data coming from canopy:
    // "command":"[cmd]","params":"[\"[output idx]\", [duty cycle ms], [period ms]]"
    
    // parse out the cmd
    if ( strstr( cmd, BN_CMD_CONTROL_STR2 ) ) {
        p = strstr( cmd, "params" );
        if ( p ) {
            p = strstr( p+1, "[\\\"" );
            if ( p ) {
                param1 = atoi(p + 3);
                p = strstr( p+1, ", " );
                param2 = atoi(p + 2);
                p = strstr( p+1, ", " );
                param3 = atoi(p + 2);
                // update the control output
                set_user_dout_pattern( param1, param2, param3 );
                // update the emaplay struct
                if ( param1 >= 4 ) {
                    emaPlay->user_douts[param1 - 4].duty = param2;
                    emaPlay->user_douts[param1 - 4].period = param3;
                }
            }
        }
    }
    else if ( strstr( cmd, BN_CMD_REBOOT_EMA_PLAY_STR ) ) {
        // set this cmd as pending, the state machine will handle it
        emaPlay->pending_cmd = BN_PENDING_CMD_REBOOT_EMA_PLAY;
    }
    else if ( strstr( cmd, BN_CMD_REBOOT_EMA_STR ) ) {
        // set this cmd as pending, the state machine will handle it
        emaPlay->pending_cmd = BN_PENDING_CMD_REBOOT_EMA;
    }
    
}

/* 
emaPlay_init_ema_cmds: 
Sends a series of emaLink cmds to initialize ema relative to this project.	
*/
void emaPlay_init_ema_cmds( struct emaLink_context *link_handle, struct ema_status *ema, struct emaPlay_status *emaPlay)
{
    // initial ema cmds
    emaLinkAT_DisableEcho( link_handle );
    emaLinkAT_GetSN( link_handle, ema->sn );
    emaLinkAT_GetFWVersion( link_handle, ema->fw_ver );
    // set the board ID
    emaPlay_create_ema_board_ID( emaPlay->sn, ema->sn, ema->board_id );
    emaLinkAT_SetBID( link_handle, ema->board_id );
}

/* 
emaPlay_create_ema_board_ID: 
Creates the Board ID string from the MCU sn and ema's serial.	
*/
void emaPlay_create_ema_board_ID( char *emaPlay_sn, char *ema_sn, char *boardID )
{
    uint8_t i, l;


    // the board id for this project is the emaPlay sn concatenated with the ema sn and converted into a string
    l = 0;
    l += sprintf( boardID+l, "emaPlay-" );
    for ( i=0; i < EMA_PLAY_SN_SIZE; i++ ) {
        l += sprintf(boardID+l, "%.2X", emaPlay_sn[i] );
    }
    l += sprintf( boardID+l, "-" );
    l += sprintf(boardID+l, "%s", ema_sn );

    // null terminate for string ops
    boardID[l+1] = 0;
}

/* 
emaPlay_check_network: 
Uses modem AT cmds to check network status	
*/
uint8_t emaPlay_check_network( struct modem_at_context *modem_handle, struct ema_status *ema )
{
    int at_st;
    uint8_t net_st = 0;

    at_st = emaModemAT_GetNetworkStatus(modem_handle, &ema->reg, ema->acc_tech, ema->act_carrier);
    if ( at_st == MOD_AT_ST_SUCCESS ) {
        if (ema->reg == 1) {
            // check signal level
            at_st = emaModemAT_GetSignalQuality( modem_handle, &ema->rxlev, &ema->ber, &ema->rscp, &ema->ecno, &ema->rsrq, &ema->rsrp );
            if ( at_st == MOD_AT_ST_SUCCESS ) {
                ema->calc_sig_q = emaPlay_ema_qualify_signal( ema->rsrq, ema->rsrp );
                emaPlay_update_led_graph( &ema->calc_sig_q );
                if ( ema->calc_sig_q != SIGNAL_UNKNOWN ) {
                    // registered and acquired signal qual
                    // update the carrier info and cntx id
                    if ( strstr( ema->act_carrier, "AT&T" ) ) {
                        ema->cntx_id = ATT_CONTEXT_ID;
                    } else if ( strstr( ema->act_carrier, "Verizon" ) ) {
                        ema->cntx_id = VZW_CONTEXT_ID;
                    }
                    net_st = 1;
                }
            }
        }else{
            emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF );
        }
    }else{
        emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF );
    }

    return net_st;
}

/* 
emaPlay_glimpse: 
Uses OptConnect Glimpse to check the network status more in detail.	
*/
uint8_t emaPlay_glimpse( struct emaLink_context *emaLink, struct ema_status *ema )
{
    int at_st;
    uint8_t net_st = 0;
    struct ema_glimpse1 glimpse1;
    struct ema_glimpse2 glimpse2;
    struct ema_glimpse4 glimpse4;
    struct ema_glimpse5 glimpse5;

    // use the the Glimpse feature to get the state of ema connectivity
    at_st = emaLinkAT_Glimpse2( emaLink, &glimpse2 );
    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
        if ( strstr(glimpse2.reg_st, "Registered, Home Network" )) {
            // registered, update the carrier info and cntx id
            if ( strstr(glimpse2.active_carr, "AT&T") ) {
                ema->cntx_id = ATT_CONTEXT_ID;
                ema->reg = 1;
                net_st = 1;
            } else if ( strstr(glimpse2.active_carr, "Verizon") ) {
                ema->cntx_id = VZW_CONTEXT_ID;
                ema->reg = 1;
                net_st = 1;
            }else{
                // unkown, fail
                ema->reg = 0;
                net_st = 0;
            }
            // updsate ema status vars
            ema->failover_time_ago = glimpse2.last_failover_ev;
            strcpy( ema->failover_st, glimpse2.failover_en );
            strcpy( ema->pri_carrier, glimpse2.pri_carr );
            strcpy( ema->cell_fw, glimpse2.active_fw );
            strcpy( ema->acc_tech, glimpse2.acc_tech );
            strcpy( ema->act_carrier, glimpse2.active_carr );
        }else{
            emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF,
                                   LED_PATTERN_PAIR_OFF );
            ema->reg = 0;
        }
    }else{
        emaPlay_set_led_array( LED_PATTERN_PAIR_SEARCHING, 
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF );
    }

    // get the current signal strength as well
    at_st = emaLinkAT_Glimpse4( emaLink, &glimpse4 );
    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
        emaPlay_update_led_graph( &glimpse4.sig_q );
        ema->calc_sig_q = glimpse4.sig_q;
    }

    // check if ema is connected to OptConnect services
    at_st = emaLinkAT_Glimpse5( emaLink, &glimpse5 );
    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
        strcpy( ema->oc_services_state, glimpse5.ema_services_state );
    }

    // get other info
    at_st = emaLinkAT_Glimpse1( emaLink, &glimpse1 );
    if ( at_st == EMA_LINK_AT_ST_SUCCESS ) {
        ema->uptime = glimpse1.ema_uptime;
        strcpy( ema->model, glimpse1.ema_model );
    }

    return net_st;
}

/* 
emaPlay_confirm_mobileIP: 
Check to see if an IP has been retrieved from the operator.	
*/
uint8_t emaPlay_confirm_mobileIP( char *IP )
{
    char *dot;
    uint8_t dot_cnt = 0;

    // just check for 3 dots for now until we know ranges
    dot = strchr( IP, '.' );
    while ( dot ) {
        dot_cnt++;
        dot = strchr( dot+1, '.' );
    }

    if ( dot_cnt == 3 ) {
        return 1;
    }else{
        return 0;
    }
}

/* 
emaPlay_init: 
Initializes ema relative to power and module on_off	
*/
void emaPlay_init( struct emaPlay_status *emaPlay )
{
    // get the gpio in the right state
    emaPlay_ema_turn_off( );
    emaPlay_ema_remove_power( );

    // reset state machine vars
    emaPlay->leaf_init = 0;
}

/* 
emaPlay_check_power: 
Checks to make sure that ema:Play has adequate voltage, which should be used before applying power to ema.	
*/
uint8_t emaPlay_check_power( struct emaPlay_stats *stat )
{
    if ( stat->sys_v < 650/*~5v adc value*/ ) {
        // loss of main power
        return 0;
    }

    // make sure load switch is enabled before checking ema voltage
    if ( 0 == gpio_get_pin_level( mod4V_dis ) ) {
        if (stat->ema_v < 2375/*~3.8V adc value*/) {
            // loss of ema power
            return 0;
        }
    }

    // power good
    return 1;
}

/* 
emaPLay_ema_apply_power: 
Applies power to ema via load switch(u6) 
*/
void emaPlay_ema_apply_power( void )
{
    emaPlay_ema_power_control( EMA_POWER_ST_ON );
}

/* 
emaPLay_ema_remove_power: 
Removes power to ema via load switch(u6) 
*/
void emaPlay_ema_remove_power( void )
{
    emaPlay_ema_power_control( EMA_POWER_ST_OFF );
}

/* 
emaPLay_ema_turn_on: 
Turns ema's cell module on. 
*/
void emaPlay_ema_turn_on( void )
{
    emaPlay_ema_on_off_control( EMA_ON_OFF_ST_ON );
}

/* 
emaPLay_ema_turn_off: 
Turns ema's cell module off 
*/
void emaPlay_ema_turn_off( void )
{
    emaPlay_ema_on_off_control( EMA_ON_OFF_ST_OFF );
}

/* 
emaPlay_ema_qualify_signal: 
Sample algorthm used to quantify signal strength 
*/
unsigned char emaPlay_ema_qualify_signal( unsigned char rsrq, unsigned char rsrp )
{
    unsigned char s_q, s_q1, s_q2;

    // place the values into a range and qualify them
    if ( rsrq == 255 || rsrq == 0 ) {
        s_q1 = SIGNAL_UNKNOWN;
    } else if (rsrq >= 20) {
        // >= 10 dB
        s_q1 = SIGNAL_EXCELLENT;
    } else if ( rsrq >= 10 ) {
        // -15 dB <= RSRQ < -10 dB
        s_q1 = SIGNAL_GOOD;
    } else if ( rsrq >= 1 ) {
        // -19.5 dB <= RSRQ < -15 dB
        s_q1 = SIGNAL_FAIR;
    } else{
        // RSRQ < -19.5 dB
        s_q1 = SIGNAL_POOR;
    }

    // place the values into a range and qualify them
    if ( rsrp == 255 || rsrp == 0 ) {
        s_q2 = SIGNAL_UNKNOWN;
    } else if (rsrp >= 60) {
        // >= -80 dBm
        s_q2 = SIGNAL_EXCELLENT;
    } else if ( rsrp >= 51 ) {
        // -90 dBm <= RSRP < -80 dBm
        s_q2 = SIGNAL_GOOD;
    } else if ( rsrp >= 41 ) {
        // -100 dBm <= RSRP < -90 dBm
        s_q2 = SIGNAL_FAIR;
    } else{
        // RSRP < -100 dBm
        s_q2 = SIGNAL_POOR;
    }

    s_q = s_q2;
    // check rsrq for 2 or lower and adjust overall
    if ( s_q1 <= SIGNAL_FAIR ) {
        if ( s_q ) {
            s_q -= 1;
        }
    }

    return s_q;
}

/* 
emaPlay_set_led_array: 
Sets the blink pattern of the led signal array 
*/
void emaPlay_set_led_array( uint16_t duty_cycle0, uint16_t period0,
                            uint16_t duty_cycle1, uint16_t period1,
                            uint16_t duty_cycle2, uint16_t period2,
                            uint16_t duty_cycle3, uint16_t period3 )
{   
    // update the entire led array
    set_led_blink_pattern( 0, duty_cycle0, period0 );
    set_led_blink_pattern( 1, duty_cycle1, period1 );
    set_led_blink_pattern( 2, duty_cycle2, period2 );
    set_led_blink_pattern( 3, duty_cycle3, period3 );
}

/* 
emaPlay_update_led_graph: 
Sets the LED signal array according to a quantified value(*sig). 
*/
void emaPlay_update_led_graph( uint8_t *sig )
{
    if ( *sig == SIGNAL_EXCELLENT ) {
        emaPlay_set_led_array( LED_PATTERN_PAIR_ON, 
                               LED_PATTERN_PAIR_ON,
                               LED_PATTERN_PAIR_ON,
                               LED_PATTERN_PAIR_ON );
    } else if ( *sig == SIGNAL_GOOD ) {
        emaPlay_set_led_array( LED_PATTERN_PAIR_ON, 
                               LED_PATTERN_PAIR_ON,
                               LED_PATTERN_PAIR_ON,
                               LED_PATTERN_PAIR_OFF );
    } else if ( *sig == SIGNAL_FAIR ) {
        emaPlay_set_led_array( LED_PATTERN_PAIR_ON, 
                               LED_PATTERN_PAIR_ON,
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF );
    } else if ( *sig == SIGNAL_POOR ) {
        emaPlay_set_led_array( LED_PATTERN_PAIR_ON, 
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF );
    }else{
        // signal none
        emaPlay_set_led_array( LED_PATTERN_PAIR_UNKNOWN, 
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF,
                               LED_PATTERN_PAIR_OFF );
    }
}

// indexs 0-3 = blu(d14-d17), 4-5 = red(d18-d19), 6-7 = user dout(pb6) & user dout(pb7) 
static uint16_t period_ticks[NUM_EMA_PLAY_LEDS+NUM_USER_OUTPUTS];
static uint16_t duty_ticks[NUM_EMA_PLAY_LEDS+NUM_USER_OUTPUTS];
static uint16_t period_cnt[NUM_EMA_PLAY_LEDS+NUM_USER_OUTPUTS];
static uint16_t duty_cnt[NUM_EMA_PLAY_LEDS+NUM_USER_OUTPUTS];

/* 
set_user_dout_pattern: 
Sets the user controllable digital output patterns 
*/
void set_user_dout_pattern(uint8_t idx, uint16_t duty_cycle, uint16_t period) 
{
    // audit the index to confirm its for the user douts
    if ( idx < 4 ) {
        return;
    }

    period_cnt[idx] = 0;
    duty_cnt[idx] = 0;
    duty_ticks[idx] = duty_cycle;
    period_ticks[idx] = period;
}

/* 
set_led_blink_pattern: 
Sets the user controllable led patterns 
*/
void set_led_blink_pattern(uint8_t idx, uint16_t duty_cycle, uint16_t period) 
{
    // audit the index to confirm its for the user douts
    if ( idx > 5 ) {
        return;
    }

    period_cnt[idx] = 0;
    duty_cnt[idx] = 0;
    duty_ticks[idx] = duty_cycle;
    period_ticks[idx] = period;
}

/* 
update_d_out: 
Updates the gpio pins used to control all digital outputs 
*/
void update_d_out(uint8_t idx) 
{
    period_cnt[idx]++;
    duty_cnt[idx]++;
    uint8_t pin;

    // figure out gpio pin
    switch (idx) {
    case 0:
        pin = blu_led0;
        break;
    case 1:
        pin = blu_led1;
        break;
    case 2:
        pin = blu_led2;
        break;
    case 3:
        pin = blu_led3;
        break;
    case 4:
        pin = red_led0;
        break;
    case 5:
        pin = red_led1;
        break;
    case 6:
        pin = pb06_user_dout0;
        break;
    case 7:
        pin = pb07_user_dout1;
        break;
    default:
        break;
    }

    if (0 == duty_ticks[idx] && 0 == period_ticks[idx]) {
        // led off
        if ( idx > 5 ) {
            // gpio
            gpio_set_pin_level(pin, false);
        }else{
            // leds
            gpio_set_pin_level(pin, true);
        }
    } else {

        // track the period
        if (period_cnt[idx] >= period_ticks[idx]) {
            // start over
            duty_cnt[idx] = 0;
            period_cnt[idx] = 0;
            if ( idx > 5 ) {
                // gpio 
                gpio_set_pin_level(pin, true);
            }else{
                // leds
                gpio_set_pin_level(pin, false);
            }
        }

        // track the duty cycle
        if (duty_cnt[idx] >= duty_ticks[idx]) {
            // turn off led
            if ( idx > 5 ) {
                // gpio
                gpio_set_pin_level(pin, false);
            }else{
                // leds
                gpio_set_pin_level(pin, true);
            }
        }
    }
}

/* 
debounce_inputs: 
Debounces all inputs(sw's, din's. sts pin). This function is called from a timer interrupt. 
*/
void debounce_inputs( void )
{
    #define SWITCH_D_TIME           100 // ms
    #define EMA_STS_PIN_D_TIME      100 // ms  
    #define SWITCH_LONG_PRESS_TIME  2000 // ms
    static uint32_t sw0_long_press_timer;
    static uint8_t sw0_start_timer = 1;
    static uint16_t sw0_d_cnt = SWITCH_D_TIME;
    static uint16_t sw1_d_cnt = SWITCH_D_TIME;
    static uint16_t din0_d_cnt = DIN_D_TIME / 2;
    static uint16_t din1_d_cnt = DIN_D_TIME / 2;
    static uint16_t status_pin_cnt = EMA_STS_PIN_D_TIME / 2;
    static uint8_t sw0_pressed = 0;
    static uint8_t sw1_pressed = 0;

    // debounce switch 0 sw0(sw7 sch.)
    if ( !gpio_get_pin_level( sw0_read ) ) {
        if ( sw0_d_cnt ) {
            sw0_d_cnt--;
        }else{
            // debounced pressed
            sw0_pressed = 1;
            sw0_d_cnt = SWITCH_D_TIME;
            // start a timer for a long press detection
            if ( sw0_start_timer ) {
                sw0_start_timer = 0;
                sw0_long_press_timer = TIME_ELAPSED_ms(0);
            }else{
                if ( (TIME_ELAPSED_ms(sw0_long_press_timer) >= SWITCH_LONG_PRESS_TIME) && (sw0_long_press == 0) ) {
                    // detected a long press
			        sw0_start_timer = 1;
                    sw0_long_press = 1;
                    // skip the release debounce
                    sw0_pressed = 0;
                }
            }
        }
    }else{
        // clear the long press timer vars
        sw0_start_timer = 1;
        if ( sw0_pressed == 1 ) {
            // debounce released
            if ( sw0_d_cnt ) {
                sw0_d_cnt--;
            }else{
                // debounced press then released
                sw0_pending = 1;
                sw0_pressed = 0;
            }
        }else{
            sw0_d_cnt = SWITCH_D_TIME / 2;
        }
    }

    // debounce switch 0 sw1(sw8 sch.)
    if ( !gpio_get_pin_level(sw1_read) ) {
        if ( sw1_d_cnt ) {
            sw1_d_cnt--;
        }else{
            // debounced pressed
            sw1_pressed = 1;
            sw1_d_cnt = SWITCH_D_TIME;
        }
    }else{
        if ( sw1_pressed == 1 ) {
            // debounce released
            if ( sw1_d_cnt ) {
                sw1_d_cnt--;
            }else{
                // debounced press then released
                sw1_pending = 1;
                sw1_pressed = 0;
            }
        }else{
            sw1_d_cnt = SWITCH_D_TIME / 2;
        }
    }

    // debounce din0(pa06) pin
    if ( gpio_get_pin_level( pa06_user_din0 ) ) {
        // high
        if ( din0_d_cnt == DIN_D_TIME ) {
            din0_state = 1;
        }else{
            din0_d_cnt++;
        }
    }else{
        // low
        if ( din0_d_cnt ) {
            din0_d_cnt--;
        }else{
            din0_state = 0;
        }
    }

    // debounce din1(pa07) pin
    if ( gpio_get_pin_level( pa07_user_din1 ) ) {
        // high
        if ( din1_d_cnt == DIN_D_TIME ) {
            din1_state = 1;
        }else{
            din1_d_cnt++;
        }
    }else{
        // low
        if ( din1_d_cnt ) {
            din1_d_cnt--;
        }else{
            din1_state = 0;
        }
    }

    // debounce ema status pin
    if ( gpio_get_pin_level( ema_sts ) ) {
        // high
        if ( status_pin_cnt == EMA_STS_PIN_D_TIME ) {
            sts_state = 1;
        }else{
            status_pin_cnt++;
        }
    }else{
        // low
        if ( status_pin_cnt ) {
            status_pin_cnt--;
        }else{
            sts_state = 0;
        }
    }
}

/* 
update_outputs: 
Updates all digital outputs. This function is called from a timer interrupt. 
*/
void update_outputs( void )
{
    uint8_t i;

    // update leds
    for ( i=0; i < NUM_EMA_PLAY_LEDS; i++ ) {
        update_d_out( i );
    }

    // update user digital outputs
    for ( i=NUM_EMA_PLAY_LEDS; i < NUM_EMA_PLAY_LEDS+NUM_USER_OUTPUTS; i++ ) {
        update_d_out( i );
    }
}


/*###########################################*/


