init

Dependencies:   aconno_I2C Lis2dh12 WatchdogTimer

main.cpp

Committer:
pathfindr
Date:
2019-05-23
Revision:
51:9078e6928412
Parent:
50:a94e70c00fff
Child:
52:bd7678eade77

File content as of revision 51:9078e6928412:

#include "main.h"
// MUST USE MBED 5.10.4   2fd0c5cfbd

/*
PRE DEPLOYMENT CHECKS!!
• Ensure APP DATA address is correct
*/


/*
TO DO
STOP FRASH WRITE FROM CRASHING IF THERE IS A PROBLEM

*/


//------------------------------------------------------------------------------
//FUNCTION PROTOTYPES - NEED TO ADD ALL OF THE MISSING ONES
//------------------------------------------------------------------------------ 
//should really add these - will just add as and when needed for now
void event_activity_tx();
void event_location_tx(bool failsafe);

//------------------------------------------------------------------------------
// GLOBALS
//------------------------------------------------------------------------------
bool             GLOBAL_debugLED                     = false;
char*            GLOBAL_defaultApi                   = "b:gps2";
bool             GLOBAL_accel_healthy                = false;
bool             GLOBAL_motionStopFlagTriggered      = false;
bool             GLOBAL_needToConfigureLis3dh        = false;
bool             GLOBAL_registeredOnNetwork          = false;
bool             GLOBAL_modemOn                      = false;
bool             GLOBAL_LEDSequenceinProgress        = false;
time_t           GLOBAL_wakeTime                     = 0;
char             GLOBAL_exceptionString[30];
char             GLOBAL_debug_buffer[200];
char             GLOBAL_failed_broadcasts[10][160];
bool             GLOBAL_failed_broadcast_slots[10];

//SETTINGS
int              RET_setting_firmware;
uint32_t         RET_setting_minimumupdate_hrs;
uint8_t          RET_setting_location_mode;
uint8_t          RET_setting_location_accuracy;
uint32_t         RET_setting_location_tx_interval_mins;
uint32_t         RET_setting_location_tx_failsafe_hrs;
uint16_t         RET_setting_location_timeout;
uint32_t         RET_setting_activity_tx_interval_hrs;
uint8_t          RET_setting_activity_mode;
uint32_t         RET_setting_environmental_tx_interval_mins;
uint16_t         RET_setting_motion_g;
time_t           RET_setting_motion_start_seconds;
time_t           RET_setting_motion_stop_seconds;
uint16_t         RET_setting_impact_g;
uint8_t          RET_setting_impact_alert;
uint16_t         RET_setting_connection_timeout;
uint16_t         RET_setting_beacon_interval_seconds;
uint16_t         RET_setting_beacon_scan;
//STATE
char             RET_pf_identifier[7];
uint8_t          RET_coldBoot = 1;
bool             RET_asleep = false;
bool             RET_busy = false;
time_t           RET_RTCunixtime;
bool             RET_haveSettings;
uint8_t          RET_state;
uint8_t          RET_state_prev;
uint8_t          RET_buttonPressCount;
time_t           RET_buttonPressTime;
time_t           RET_buttonReleaseTime;
time_t           RET_buttonReleaseTime_prev;
time_t           RET_buttonHoldTime;
time_t           RET_SetupRunAt;
time_t           RET_SettingsGotAt;
bool             RET_setupInProgress;
bool             RET_force2G;
bool             RET_watchdogfired;
bool             RET_receivedNewSettings;
uint32_t         RET_GPSFailCount;
uint32_t         RET_NetworkFailCount;
bool             RET_debug = true;
time_t           RET_debug_offat;
float            RET_voltage;
float            RET_temperature;
float            RET_temperature_max = -999.0; //Within broadcast frame. Set inital value to low
float            RET_temperature_min = 999.0; //Within broadcase frame. Set inital value to high
//MOTION STATE
bool             RET_motionTriggeredinFrame;
bool             RET_motionTriggeredInLocTXInterval;
time_t           RET_motionStartTime;
time_t           RET_motionStopTime;
bool             RET_motionPendingOnState;
bool             RET_motionPendingOffState;
bool             RET_motionState;
float            RET_motionTotalActivityHours;
float            RET_motionTotalActivityHoursSincePost;
time_t           RET_motionFrameStart;
char             RET_activityData[ACTIVITY_BUFFERSIZE];
//IMPACT
bool             RET_impactTriggered;
//EVENTS LOGGING
time_t           RET_eventTime_location_log;
time_t           RET_eventTime_environmental_log;
//EVENTS TX
time_t           RET_eventTime_location_tx;
time_t           RET_eventTime_location_failsafe_tx;
time_t           RET_eventTime_environmental_tx;
time_t           RET_eventTime_activity_tx;
time_t           RET_eventTime_wakeFromDormant;

//------------------------------------------------------------------------------
//GPIO
//------------------------------------------------------------------------------ 
InterruptIn button(PN_IN_BUTTON);

//------------------------------------------------------------------------------
//PERIPHERALS
//------------------------------------------------------------------------------ 
WatchdogTimer watchdog; //Do not set to less than 4500ms or can cause issues with softdevice
void watchdogKick() { watchdog.kick();}
//SI7060 si7060(PN_I2C_SDA, PN_I2C_SCL);
LIS3DH lis3dh(PN_SPI_MOSI, PN_SPI_MISO, PN_SPI_CS0, PN_SPI_CLK);
Modem modem(PN_GSM_PWR_KEY, PN_VREG_EN, PN_GSM_WAKE_DISABLE);
LowPowerTicker RTCticker;
LowPowerTimer LPtimer;

//------------------------------------------------------------------------------
//THREAD SEMAPHORES
//------------------------------------------------------------------------------ 
Semaphore mainthread;

//------------------------------------------------------------------------------
// LOW LEVEL FUNCS
//------------------------------------------------------------------------------
void set_8bit_flag(uint8_t& flags, uint8_t flag) {
    flags |= flag;
}
bool get_8bit_flag(uint8_t& flags, uint8_t flag) {
    bool result = (flags & flag);
    return result;
}
void clr_8bit_flag(uint8_t& flags, uint8_t flag) {
    flags &= ~flag;
}
void nrf_configureForSleep(){
    //Disable SPI pins to reduce power
    //nrf_gpio_cfg_input(PN_SPI_MOSI, NRF_GPIO_PIN_NOPULL);  //Don't need this one
    nrf_gpio_cfg_input(PN_SPI_MISO, NRF_GPIO_PIN_NOPULL);
    
    //disable modem control lines
    nrf_gpio_cfg_input(PN_GSM_PWR_KEY, NRF_GPIO_PIN_NOPULL);
    nrf_gpio_cfg_input(PN_GSM_WAKE_DISABLE, NRF_GPIO_PIN_NOPULL);
    
    /*
    //ERRATA 89, shut down i2c channels properly
    //TWI0
    //NRF_TWI0->ENABLE=0;//TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
    //NRF_TWI0->ENABLE=1;//TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
    *(volatile uint32_t *)0x40003FFC = 0;
    *(volatile uint32_t *)0x40003FFC;
    *(volatile uint32_t *)0x40003FFC = 1;
    //TWI1
    //NRF_TWI1->ENABLE=TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;
    *(volatile uint32_t *)0x40004FFC = 0;
    *(volatile uint32_t *)0x40004FFC;
    *(volatile uint32_t *)0x40004FFC = 1;
    */
}
void setState(uint8_t state) {
    RET_state_prev = RET_state;
    RET_state = state;
}
void dumpSettings() {  
    if(RET_debug){
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_RTCunixtime:%u", RET_RTCunixtime);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_firmware:%d", RET_setting_firmware);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_minimumupdate_hrs:%d", RET_setting_minimumupdate_hrs);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_state:%d", RET_state);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_location_mode:%d", RET_setting_location_mode);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_location_accuracy:%d", RET_setting_location_accuracy);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_location_tx_interval_mins:%d", RET_setting_location_tx_interval_mins);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_location_tx_failsafe_hrs:%d", RET_setting_location_tx_failsafe_hrs);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_location_timeout:%d", RET_setting_location_timeout);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_activity_tx_interval_hrs:%d", RET_setting_activity_tx_interval_hrs);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_activity_mode:%d", RET_setting_activity_mode);debug_exe();        
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_environmental_tx_interval_mins:%d", RET_setting_environmental_tx_interval_mins);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_motion_g:%d", RET_setting_motion_g);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_motion_start_seconds:%d", RET_setting_motion_start_seconds);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_motion_stop_seconds:%d", RET_setting_motion_stop_seconds);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_impact_g:%d", RET_setting_impact_g);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_impact_alert:%d", RET_setting_impact_alert);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_connection_timeout:%d", RET_setting_connection_timeout);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_beacon_interval_seconds:%d", RET_setting_beacon_interval_seconds);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_setting_beacon_scan:%d", RET_setting_beacon_scan);debug_exe();
        debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RET_pf_identifier:%s", RET_pf_identifier);debug_exe();
    }
}
void recordFirmwareAsValid() {
    debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RECORD FW %d AS VALID...", FW_VERSION);debug_exe();
    read_app_data_from_flash(&app_data);
    bool write_app_data_to_flash_execute = false;
    if(get_flag(&app_data, app_execution_flag) == true) {
        clr_flag(&app_data, app_execution_flag);
        clr_flag(&app_data, first_run_flag);
        write_app_data_to_flash_execute = true;
    }
    if(app_data.current_firmware_version != FW_VERSION) {
        app_data.current_firmware_version = FW_VERSION;
        app_data.target_firmware_version = FW_VERSION;
        write_app_data_to_flash_execute = true;
    }
    if (write_app_data_to_flash_execute) {
        write_app_data_to_flash(&app_data);
        //read back to check
        ThisThread::sleep_for(200); //need this delay
        read_app_data_from_flash(&app_data);
        if (app_data.current_firmware_version == FW_VERSION) {
            debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "OK");debug_exe();
        } else {
            debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "FAIL - IS BLE SHUTDOWN?");debug_exe();
        }
    }
}
void updateBatteryV() {
    NRF52_SAADC batteryIn;
    batteryIn.addChannel(9); // vdd for battery
    batteryIn.calibrate();
    //get initial reading, first reading is always low
    batteryIn.updateData(); batteryIn.getData()[0];
    float voltage_accumulator = 0.0;
    uint8_t readings = 0;
    for (uint8_t i = 1; i <= 50; i++) { // need to get it 2 times to get accurate data, first one is always low for some reason
        batteryIn.updateData();
        voltage_accumulator += (batteryIn.getData()[0])*(1.0/1024.0)*3.60;
        readings ++;
    }
    RET_voltage = (voltage_accumulator / (float)readings);
}
float nrfTemperature() {
    float temperature = 0.0;
    
    //INTERNAL NRF52 TEMP SENSOR
    uint32_t safetycounter = 0;
    NRF_TEMP->TASKS_START=1;
    while (NRF_TEMP->EVENTS_DATARDY==0 && safetycounter < 10000) {
        safetycounter ++;
    };
    NRF_TEMP->EVENTS_DATARDY=0;
    temperature = nrf_temp_read()/4.0;
    NRF_TEMP->TASKS_STOP=1;
    
    //SOFTDEVICE METHOD - To use this method you need to include #include "nrf_soc.h"
    //THIS GAVE SOME INTERMITENT ODD RESULTS AND DOESNT SEEM AS RELIABLE
    /*
    int32_t t;
    sd_temp_get(&t);
    temperature = t/4.0;
    */
    
    return temperature;
}
float getTemperature() {
    float temperature;
    if (USE_NRF_TEMP_SENSOR) {
        temperature = nrfTemperature();
    } else {
        //temperature = si7060.getTemperature(); //currently disabled because its causing a high current 450ua sleep, most likely due to sensor not sleeping correctly, or i2c sleep issue
    }
    return temperature;  
}
void updateTemperatures() {
    //RET_temperature = getTemperature(); //not smoothed
    if (RET_temperature == 0.0f) {RET_temperature = getTemperature();}
    RET_temperature = (RET_temperature + (getTemperature() - RET_temperature) * 0.25f); //smoothed
    
    if (RET_temperature < RET_temperature_min) {
        RET_temperature_min = RET_temperature;
    }
    if (RET_temperature > RET_temperature_max) {
        RET_temperature_max = RET_temperature;
    }
    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "Temp:%.1f  Min:%.1f  Max:%.1f",RET_temperature,RET_temperature_min,RET_temperature_max);debug_exe();}
}
void addToExceptionString(char* value) {
    if(strstr(GLOBAL_exceptionString, value) == false){
        snprintf(GLOBAL_exceptionString+strlen(GLOBAL_exceptionString),sizeof(GLOBAL_exceptionString),"%s.",value);
    }
}
void resetTemperatures() {
    RET_temperature_max = -999.0; //Within broadcast frame. Set inital value to low
    RET_temperature_min = 999.0; //Within broadcase frame. Set inital value to high   
}
//------------------------------------------------------------------------------
// USER BUTTON HANDLING
//------------------------------------------------------------------------------ 
void buttonPress() {
    RET_buttonPressTime = LPtimer.read_ms();
}
void buttonRelease() {
    //debounce catch
    RET_buttonReleaseTime = LPtimer.read_ms();
    if ((RET_buttonReleaseTime - RET_buttonReleaseTime_prev) > 75) { 
        RET_buttonPressCount ++;
        RET_buttonHoldTime = (RET_buttonReleaseTime - RET_buttonPressTime);
        //temporarily enable debugging and dump settings
        RET_debug = true;
        RET_debug_offat = (RET_RTCunixtime + 600); //debug on for 10 mins
    }
    RET_buttonReleaseTime_prev = RET_buttonReleaseTime;
}
//------------------------------------------------------------------------------
// RTC TICKER
//------------------------------------------------------------------------------
void RTCtick() {
    //YOU MUST NOT CALL ANY OTHER FUNCTIONS OR DEBUG FROM INSIDE HERE!!! OR IT LOCKS UP THE DEVICE, just change vars & comparisons etc
    RET_RTCunixtime += 1;
    
    //button logic
    if (RET_buttonHoldTime >= 4000 && RET_buttonHoldTime <= 10000) {
        RET_buttonHoldTime = 0;
        RET_buttonPressCount = 0;
        if (RET_state != STATE_DORMANT) {
            RET_state_prev = RET_state;
            RET_state = STATE_TURNOFF;
        } else {
            RET_state = STATE_TURNON;
        }
        mainthread.release();
    } else if (RET_buttonHoldTime >= 14000 && RET_buttonHoldTime <= 30000) {
        RET_buttonHoldTime = 0;
        RET_buttonPressCount = 0;
        RET_state = STATE_SCORCHEDEARTH;
        mainthread.release();
    } else {
        if((LPtimer.read_ms() - RET_buttonReleaseTime) > 1500 && RET_buttonPressCount > 0) {
            if(RET_busy == true) { 
                //RTCtick_ledflash_count = 4;// 2 flashes
            } else {
                RET_SetupRunAt = 0; //allow setup to run again
                switch (RET_buttonPressCount) {   //double catches to help with debounce
                    case 1 :
                        if(RET_state_prev != RET_state) {RET_state_prev = RET_state;}
                        RET_state = STATE_BUTTONPRESS1;
                        RET_buttonPressCount = 0;
                        mainthread.release();
                        break;
                    case 3 :
                        if(RET_state_prev != RET_state) {RET_state_prev = RET_state;}
                        RET_state = STATE_BUTTONPRESS3;
                        RET_buttonPressCount = 0;
                        mainthread.release();
                        break;
                    case 5 :
                        if(RET_state_prev != RET_state) {RET_state_prev = RET_state;}
                        RET_state = STATE_BUTTONPRESS5;
                        RET_buttonPressCount = 0;
                        mainthread.release();
                        break;
                    default :
                        //do nothing
                        break;
                }
            }
            RET_buttonPressCount = 0;
        }
    }
}

void resetGlobals() {
    GLOBAL_accel_healthy = false;
    GLOBAL_motionStopFlagTriggered = false;
    memset(GLOBAL_exceptionString,0x00,sizeof(GLOBAL_exceptionString));
}
void healthCheck() {
    //check for watchdog fire
    if (RET_watchdogfired == true) {
        addToExceptionString("WD");
        RET_watchdogfired = false;
    }
    //check clock
    if(RET_haveSettings == true) {
        if (RET_RTCunixtime < 1547678732) {
            //go dormant for 72hrs and then resetup
            RET_haveSettings = false; 
            setState(STATE_DORMANT);
        }
    }
    //check that we have a reasonable time to loc
    if(RET_haveSettings == true) {
        //check location tx delta is set
        if (RET_eventTime_location_tx == 0) {
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "ERR:eventTime_location_tx is 0");debug_exe();}
            RET_haveSettings = false;
        }
        //check location failsafe tx delta is less that 2 weeks
        long location_failsafe_tx_delta = (RET_eventTime_location_failsafe_tx - RET_RTCunixtime);
        if (location_failsafe_tx_delta > TENDAYSINSECONDS) {
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "ERR:location_failsafe_tx_delta too small");debug_exe();}
            RET_haveSettings = false;
        }
    }
    if (RET_haveSettings == false) {
        setState(STATE_SETUP);
    }
}
void setDefaults() {
    //STATE
    RET_asleep = false;
    RET_busy = false;
    RET_haveSettings = false;
    RET_state = STATE_SETUP;
    RET_state_prev = RET_state;
    RET_RTCunixtime = 0;
    RET_SetupRunAt = 0;
    RET_SettingsGotAt = 0;
    RET_force2G = DEFAULT_FORCE2G;
    RET_watchdogfired = false;
    RET_setupInProgress = false;
    RET_receivedNewSettings = false;
    RET_GPSFailCount = 0;
    RET_NetworkFailCount = 0;
    //SETTINGS
    RET_setting_firmware = 0;
    RET_setting_minimumupdate_hrs = 0;
    RET_setting_location_mode = DEFAULT_LOCATION_MODE;
    RET_setting_location_accuracy = DEFAULT_LOCATION_ACCURACY;
    RET_setting_location_tx_interval_mins = DEFAULT_LOCATION_TX_INTERVAL_MINS;
    RET_setting_location_tx_failsafe_hrs = DEFAULT_LOCATION_TX_FAILSAFE_HRS;
    RET_setting_location_timeout = DEFAULT_LOCATION_TIMEOUT;
    RET_setting_activity_tx_interval_hrs = 0;
    RET_setting_activity_mode = 1;
    RET_setting_environmental_tx_interval_mins = 0;
    RET_setting_motion_g = DEFAULT_MOTION_G;
    RET_setting_motion_start_seconds = DEFAULT_MOTION_START_SECONDS;
    RET_setting_motion_stop_seconds = DEFAULT_MOTION_STOP_SECONDS;
    RET_setting_impact_g = 0;
    RET_setting_impact_alert = 0;
    RET_setting_connection_timeout = DEFAULT_CONNECTION_TIMEOUT;
    RET_setting_beacon_scan = 0;
    RET_buttonPressCount = 0;
    RET_buttonPressTime = 0;
    RET_buttonReleaseTime = 0;
    RET_buttonHoldTime = 0;
    //MOTION STATE
    RET_motionTriggeredinFrame = false;
    RET_motionTriggeredInLocTXInterval = false;
    RET_motionStartTime = 0;
    RET_motionStopTime = 0;
    RET_motionPendingOnState = 0;
    RET_motionPendingOffState = 0;
    RET_motionState = 0;
    RET_motionTotalActivityHours = 0.0;
    RET_motionTotalActivityHoursSincePost = 0.0;
    RET_motionFrameStart = 0;
    memset(RET_activityData,0x00,sizeof(RET_activityData));
    //IMPACT
    RET_impactTriggered = 0;
    //EVENT HANDLING
    RET_eventTime_location_log = 0;
    RET_eventTime_location_tx = 0;
    RET_eventTime_location_failsafe_tx = 0;
    RET_eventTime_environmental_log = 0;
    RET_eventTime_environmental_tx = 0;
    RET_eventTime_activity_tx = 0;
    RET_eventTime_wakeFromDormant = 0;
    //PERIPHERAL RESET
    lis3dh_configureForSleep(DEFAULT_MOTION_G,DEFAULT_IMPACT_G);
}

bool selfTest() {
    int test_count = 0;
    int test_pass = 0;
    
    //Accelerometer
    test_count ++;
    uint8_t lis3dh_id; 
    lis3dh.LIS3DH_GetWHO_AM_I(&lis3dh_id);
    if (lis3dh_id == 51) {
        GLOBAL_accel_healthy = true;
        test_pass ++;
    } else {
        GLOBAL_accel_healthy = false;
        addToExceptionString("A");   
    }
    
    //Result
    if (test_count == test_pass) {
        return true;
    } else {
        addToExceptionString("HF"); 
        return false;   
    }
}

//------------------------------------------------------------------------------
// MOTION FUNCS
//------------------------------------------------------------------------------ 
bool checkMotion() {
    if (RET_haveSettings) { //NEED THIS AS THINGS GO FUNKY WITHOUT RTC TIME SET CORRECTLY
        if (lis3dh_int2) {
            RET_motionTriggeredinFrame = true;
            GLOBAL_needToConfigureLis3dh = true; //interrupt has fire so need to clear it
            if (!RET_motionPendingOnState) {
                RET_motionPendingOnState = true;
                RET_motionPendingOffState = false;
                // Log start motion time
                RET_motionStartTime = RET_RTCunixtime;
            }
        } else {
            RET_motionTriggeredinFrame = false;
            RET_motionPendingOnState = false;
            if (!RET_motionPendingOffState) {
                RET_motionPendingOffState = true;
                //log stop motion time
                RET_motionStopTime = RET_RTCunixtime;
            }
        }
        //calculate motion state
        if (RET_motionFrameStart == 0) {RET_motionFrameStart = RET_RTCunixtime;}
        if (RET_motionPendingOnState) {
            //check if above threshold
            time_t inMotionForSeconds = ((RET_RTCunixtime - RET_motionStartTime) + (DEFAULT_SLEEP_FRAME / 1000)); //Plus DEFAULT_SLEEP_FRAME as it should include frame time
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "MOT:%u s",inMotionForSeconds);debug_exe();}
            if (inMotionForSeconds >= (RET_setting_motion_start_seconds + 15) && RET_motionState == false) {
                RET_motionState = true;
                RET_motionTriggeredInLocTXInterval = true;
                if (RET_setting_activity_mode == 2 && RET_setting_activity_tx_interval_hrs > 0) {
                    time_t epochOffsetMins = ((RET_RTCunixtime - RET_motionFrameStart) / 60);
                    sprintf(RET_activityData+strlen(RET_activityData),"1.%u!",epochOffsetMins);
                }
            }
        }
        if (RET_motionPendingOffState) {
            time_t noMotionForSeconds = ((RET_RTCunixtime - RET_motionStopTime) + (DEFAULT_SLEEP_FRAME / 1000)); //Plus DEFAULT_SLEEP_FRAME as it should include frame time
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "MOTLESS:%u s",noMotionForSeconds);debug_exe();}
            if (noMotionForSeconds >= (RET_setting_motion_stop_seconds + 15) && RET_motionState == true) {
                RET_motionPendingOffState = false;
                RET_motionState = false;
                GLOBAL_motionStopFlagTriggered = true;
                if (RET_setting_activity_mode > 0 && RET_setting_activity_tx_interval_hrs > 0) {
                    RET_motionTotalActivityHoursSincePost += ((float(RET_motionStopTime)-float(RET_motionStartTime)) / 3600.0f);
                    RET_motionTotalActivityHours += RET_motionTotalActivityHoursSincePost;
                    if (RET_setting_activity_mode == 2) {
                        time_t epochOffsetMins = ((RET_RTCunixtime - RET_motionFrameStart) / 60);
                        sprintf(RET_activityData+strlen(RET_activityData),"0.%u!",epochOffsetMins);
                    }
                }
            }
        }
    }
    return RET_motionState;
}

//------------------------------------------------------------------------------
// UPDATE OPERATING SETTINGS
//------------------------------------------------------------------------------ 
bool saveSettings(char* settingsBuffer) {
    int matchCount = 0;
    int critical_fail_count = 0;
    int TEMP_a = -1; time_t TEMP_b = 0; int TEMP_c = -1; int TEMP_d = -1; int TEMP_e = -1; int TEMP_f = -1; int TEMP_g = -1; int TEMP_h = -1; int TEMP_i = -1; int TEMP_j = -1; 
    int TEMP_k = -1; int TEMP_l = -1; int TEMP_m = -1; int TEMP_n = -1; int TEMP_o = -1; int TEMP_p = -1; int TEMP_q = -1; int TEMP_r = -1; int TEMP_s = -1; int TEMP_t = -1; char TEMP_u[7];
    
    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "RESP: %s",settingsBuffer);debug_exe();}
    
    //exampple
    //+CUSD: 0,”1#a:12,b:1558020598,c:-1,d:-1,e:1,f:2,g:5,h:168,i:90,j:168,k:0,l:8,m:60,n:60,o:0,p:0,q:120,r:0,s:0,t:1,u:F859C1#”,15    
    
    if ( (matchCount = sscanf(settingsBuffer,"a:%d,b:%u,c:%d,d:%d,e:%d,f:%d,g:%d,h:%d,i:%d,j:%d,k:%d,l:%d,m:%d,n:%d,o:%d,p:%d,q:%d,r:%d,s:%d,t:%d,u:%s",
    &TEMP_a,&TEMP_b,&TEMP_c,&TEMP_d,&TEMP_e,&TEMP_f,&TEMP_g,&TEMP_h,&TEMP_i,&TEMP_j,&TEMP_k,&TEMP_l,&TEMP_m,&TEMP_n,&TEMP_o,&TEMP_p,&TEMP_q,&TEMP_r,&TEMP_s,&TEMP_t,&TEMP_u) ) > 0 ) {
        
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "PARAMS: a:%d,b:%u,c:%d,d:%d,e:%d,f:%d,g:%d,h:%d,i:%d,j:%d,k:%d,l:%d,m:%d,n:%d,o:%d,p:%d,q:%d,r:%d,s:%d,t:%d,u:%s\n",
        TEMP_a,TEMP_b,TEMP_c,TEMP_d,TEMP_e,TEMP_f,TEMP_g,TEMP_h,TEMP_i,TEMP_j,TEMP_k,TEMP_l,TEMP_m,TEMP_n,TEMP_o,TEMP_p,TEMP_q,TEMP_r,TEMP_s,TEMP_t,TEMP_u);debug_exe();}
        
        if(TEMP_a != -1) { RET_setting_minimumupdate_hrs = TEMP_a;                      }
        if(TEMP_b > 1552915630) {
            if (RET_RTCunixtime < 1552915630) {
                //assume this is first proper unixtime and set
                RET_RTCunixtime = TEMP_b;
            } else {
                //this is not a first unixtime, so check within safe range, convert to long long first to avoid -1 rollover issue
                long long unixtime_in = TEMP_b;
                long long unixtime_current = RET_RTCunixtime;
                if ((unixtime_in - unixtime_current) < 604800) { //allow 1 week difference
                    RET_RTCunixtime = TEMP_b;
                } else {
                    critical_fail_count++;
                    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "unixtime too far ahead. C:%u  N:%u",RET_RTCunixtime,TEMP_b);debug_exe();}
                }
            }
        } else { 
            critical_fail_count++;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "unixtime too low");debug_exe();}
        }
        if(TEMP_c != -1) { RET_setting_firmware = TEMP_c;                               } else { RET_setting_firmware = -1;}
        if(TEMP_d != -1) { setState(TEMP_d);                                            } else { setState(STATE_NORMAL);}
        if(TEMP_e != -1) { RET_setting_location_mode = TEMP_e;                          }
            if(RET_setting_location_mode > 3) {RET_setting_location_mode = DEFAULT_LOCATION_MODE;}
        if(TEMP_f != -1) { RET_setting_location_accuracy = TEMP_f;                      }
            if(RET_setting_location_accuracy > 3) {RET_setting_location_accuracy = DEFAULT_LOCATION_ACCURACY;}
        if(TEMP_g != -1) { RET_setting_location_tx_interval_mins = TEMP_g;              }
        if(TEMP_h != -1) { RET_setting_location_tx_failsafe_hrs = TEMP_h;               }
            if(RET_setting_location_tx_failsafe_hrs > 504) {RET_setting_location_tx_failsafe_hrs = DEFAULT_LOCATION_TX_FAILSAFE_HRS;}
        if(TEMP_i != -1) { RET_setting_location_timeout = TEMP_i;                       }
            if(RET_setting_location_timeout < 60 || RET_setting_location_timeout > 300) {RET_setting_location_timeout = DEFAULT_LOCATION_TIMEOUT;}
        if(TEMP_j != -1) { RET_setting_activity_tx_interval_hrs = TEMP_j;               }
        if(TEMP_k != -1) { RET_setting_environmental_tx_interval_mins = TEMP_k;         }
        if(TEMP_l != -1) { RET_setting_motion_g = TEMP_l;                               }
            if(RET_setting_motion_g < 4 || RET_setting_motion_g > 127) {RET_setting_motion_g = DEFAULT_MOTION_G;}
        if(TEMP_m != -1) { RET_setting_motion_start_seconds = TEMP_m;                   }
        if(TEMP_n != -1) { RET_setting_motion_stop_seconds = TEMP_n;                    }
        if(TEMP_o != -1) { RET_setting_impact_g = TEMP_o;                               }
        if(TEMP_p != -1) { RET_setting_impact_alert = TEMP_p;                           }
        if(TEMP_q != -1) { RET_setting_connection_timeout = TEMP_q;                     }
            if(RET_setting_connection_timeout < 60 || RET_setting_connection_timeout > 240) { RET_setting_connection_timeout = DEFAULT_CONNECTION_TIMEOUT; }
        if(TEMP_r != -1) { RET_setting_beacon_interval_seconds = TEMP_r;                }
        if(TEMP_s != -1) { RET_setting_beacon_scan = TEMP_s;                            }
        if(TEMP_t != -1) { RET_setting_activity_mode = TEMP_t;                          }
        if(TEMP_u[0] != 0x00 && TEMP_u[1] != 0x00 && TEMP_u[2] != 0x00) { strcpy(RET_pf_identifier,TEMP_u);                      }

        if (critical_fail_count == 0) { 
            RET_receivedNewSettings = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "NEW SETTINGS");debug_exe();}
            dumpSettings();
            RET_haveSettings = true;
            GLOBAL_needToConfigureLis3dh = true;
            RET_SettingsGotAt = RET_RTCunixtime;
            //check for firmware update
            if (RET_setting_firmware > 0 && RET_setting_firmware != FW_VERSION) {
                //Broadcast any activity data we have before fw update
                event_activity_tx();
                //prep for FW update
                read_app_data_from_flash(&app_data);
                clr_flag(&app_data, app_execution_flag);
                clr_flag(&app_data, first_run_flag);
                app_data.current_firmware_version = FW_VERSION;
                app_data.target_firmware_version = RET_setting_firmware;
                write_app_data_to_flash(&app_data);
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "OTA\n");debug_exe();}
                ThisThread::sleep_for(200);
                system_reset();
            }
            return true;
        } else {
            addToExceptionString("SCF");
            return false;
        }
    } else {
        addToExceptionString("SPE");
        return false;
    }
}

//------------------------------------------------------------------------------
// SET EVENT TIMES
//------------------------------------------------------------------------------ 
void setEventTime_Location() {
    if(RET_setting_location_tx_interval_mins > 0) { 
        RET_eventTime_location_tx = (RET_RTCunixtime + (RET_setting_location_tx_interval_mins * 60));
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "EVTSET - LOC TX @ %u, MODE %d",RET_eventTime_location_tx, RET_setting_location_mode);debug_exe();}
    }
    if(RET_setting_location_tx_failsafe_hrs > 0) { 
        RET_eventTime_location_failsafe_tx = (RET_RTCunixtime + (RET_setting_location_tx_failsafe_hrs * 3600));
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "EVTSET - LOC FS TX @ %u",RET_eventTime_location_failsafe_tx);debug_exe();}
    }
}
void setEventTime_Activity() {
    if(RET_setting_activity_tx_interval_hrs > 0) { 
        RET_eventTime_activity_tx = (RET_RTCunixtime + (RET_setting_activity_tx_interval_hrs * 3600));
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "EVTSET - ACT TX @ %u",RET_eventTime_activity_tx);debug_exe();}
    }
}
void setEventTime_Environmental() {
    if(RET_eventTime_environmental_tx > 0) { 
        RET_eventTime_environmental_tx = (RET_RTCunixtime + (RET_setting_environmental_tx_interval_mins * 60));
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "EVTSET - ENV TX @ %u",RET_eventTime_environmental_tx);debug_exe();}
    }
}
void setEventTimes() {
    //SET ALL EVENT TIMES
    setEventTime_Location();
    setEventTime_Activity();
    setEventTime_Environmental();
}

//------------------------------------------------------------------------------
// ERROR LOG
//------------------------------------------------------------------------------ 
void sendErrorLog(char* error) {
    updateBatteryV();
    int timetaken = (RET_RTCunixtime - GLOBAL_wakeTime);
    char bytestosend[100];
    memset(bytestosend,0x00,sizeof(bytestosend));
    snprintf(bytestosend,sizeof(bytestosend),"(%s,a:log,f:%d,v:%.2f,e:%d,z:%s.%s,c:1,s:0)\0",GLOBAL_defaultApi,FW_VERSION,RET_voltage,timetaken,error,GLOBAL_exceptionString);
    if (modem.on(RET_force2G)) {
        if (modem.registerOnNetwork(DEFAULT_CONNECTION_ATTEMPTS,RET_setting_connection_timeout,RET_NetworkFailCount)) {
            modem.USSDmessage(bytestosend, false, GLOBAL_defaultApi);
        }
    }
    ThisThread::sleep_for(250);
}

//------------------------------------------------------------------------------
// EVENTS
//------------------------------------------------------------------------------ 
void failed_broadcast_log(char* eventstring) {
    //First available free slot
    bool saved = false;
    for(int i = 0; i < sizeof(GLOBAL_failed_broadcast_slots); i++) {
        if (GLOBAL_failed_broadcast_slots[i] == false) {
            memset(GLOBAL_failed_broadcasts[i],0x00,sizeof(GLOBAL_failed_broadcasts[i])); //clear row
            snprintf(GLOBAL_failed_broadcasts[i],sizeof(GLOBAL_failed_broadcasts[i]),"%s",eventstring); //save new row
            GLOBAL_failed_broadcast_slots[i] = true;
            saved = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"Failed Broadcast saved: %s",eventstring);debug_exe();}
            break;
        }
    }
    if (!saved) {
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"Failed Broadcast save fail");debug_exe();}
    }
    return;
}
void failed_broadcasts_tx() {
    //check we have something to send...
    int numbertosend = 0;
    for(int i = 0; i < sizeof(GLOBAL_failed_broadcast_slots); i++) {
        if (GLOBAL_failed_broadcast_slots[i] == true) {
            numbertosend ++;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"Retry:%s",GLOBAL_failed_broadcasts[i]);debug_exe();}
        }
    }
    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"Failed Broadcasts to send:%d",numbertosend);debug_exe();}
    for(int i = 0; i < sizeof(GLOBAL_failed_broadcast_slots); i++) {
        if (GLOBAL_failed_broadcast_slots[i] == true) {
            if (RET_NetworkFailCount <= DEFAULT_MAX_FAILED_CONNECTIONS) {
                if (modem.on(RET_force2G)) {
                    if (modem.registerOnNetwork(2,RET_setting_connection_timeout,RET_NetworkFailCount)) {
                        char result[100];
                        bool getResponse = false;
                        snprintf(result,sizeof(result),"%s",modem.USSDmessage(GLOBAL_failed_broadcasts[i], getResponse, GLOBAL_defaultApi));
                        if (strcmp(result, "sendok") == 0) {
                            //clear row
                            memset(GLOBAL_failed_broadcasts[i],0x00,sizeof(GLOBAL_failed_broadcasts[i]));
                            GLOBAL_failed_broadcast_slots[i] = false;
                        }
                    }
                }
            }
        }
    }
}
bool event_setup(bool manualrun) {
    bool pass = false;
    LED1on(0);
    if (RET_NetworkFailCount > DEFAULT_MAX_FAILED_CONNECTIONS) addToExceptionString("NF");
    if (RET_GPSFailCount > DEFAULT_MAX_FAILED_GPS) addToExceptionString("GF");
    RET_setupInProgress = true;
    RET_NetworkFailCount = 0;
    RET_GPSFailCount = 0;
    RET_setting_connection_timeout = 180; //reset to longer value to setup run to help with connection
    bool selftestresult = selfTest();
    int connectionStartTime = RET_RTCunixtime;
    if (modem.on(RET_force2G)) {
        if (modem.registerOnNetwork(DEFAULT_CONNECTION_ATTEMPTS,RET_setting_connection_timeout,RET_NetworkFailCount)) {
            int timetaken_total = (RET_RTCunixtime - GLOBAL_wakeTime) + 10; //add 10 for ussd response time.
            int timetaken_connection = (RET_RTCunixtime - connectionStartTime);
            if (manualrun) addToExceptionString("MAN");
            char bytestosend[160];
            snprintf(bytestosend,sizeof(bytestosend),"(%s,a:setup,f:%d,v:%.2f,t:%.1f,e:%d,y:%d,z:SETUP-%s,k:%s,m:%s,c:1,s:1)\0",GLOBAL_defaultApi,FW_VERSION,RET_voltage,RET_temperature,timetaken_total,timetaken_connection,GLOBAL_exceptionString,SKU,HW_MAJORREVISION);
            char result[200];
            snprintf(result,sizeof(result),"%s",modem.USSDmessage(bytestosend, true, GLOBAL_defaultApi));
            RET_setupInProgress = false; //this turns off the flashing led
            if (strcmp(result, "sendokrecfail") == 0) {
                //send ok but receive fail
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"USSD Send Ok but Rec fail");debug_exe();}
                sendErrorLog("ERR-SU-NR");
            } else if (strcmp(result, "sendfail") == 0) {
                //do nothing
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"USSD Send Fail");debug_exe();}
            } else {
                if (saveSettings(result) == true) {
                    pass = true;
                } else {
                    //something went critically wrong getting settings 
                    sendErrorLog("ERR-SU-IR");
                }
            }
        }
    } else {
        //FAILUREMODE Modem failed to turn on  
        if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"Modem Fail");debug_exe();}
    }
    
    //IF WE PASSED THEN LETS DO OTHER POSTS
    if (pass == true) {
        GLOBAL_wakeTime = RET_RTCunixtime; //lets reset this here so that the following loc and act function have sensible values for total time
        //GET LOC DATA
        uint8_t previous_location_accuracy = RET_setting_location_accuracy;
        RET_setting_location_accuracy = 1; //set location mode to CL only
        event_location_tx(false);
        RET_setting_location_accuracy = previous_location_accuracy; //set location mode back to previous
        //SEND ANY ACTIVITY DATA
        event_activity_tx();
        LED1blink(4,500); //PASS AND END
    }
    
    //TIDY UP = LOG RUN TIME - THIS MUST GO AT END AFTER WE HAVE GOT SERVER TIMESTAMP
    RET_SetupRunAt = RET_RTCunixtime;
    RET_GPSFailCount = 0;
    RET_NetworkFailCount = 0;
    //RESULT
    return pass;
}
void event_turnonofflog_tx(bool turnon) {
    RET_NetworkFailCount = 0; //reset network blocker
    RET_GPSFailCount = 0;
    updateBatteryV();
    int connectionStartTime = RET_RTCunixtime;
    if (modem.on(RET_force2G)) {
        char locString[70];
        int gpsStartTime = RET_RTCunixtime;
        memcpy(locString, modem.getLocation(1, RET_setting_location_timeout, RET_GPSFailCount, RET_NetworkFailCount), sizeof(locString));
        int timetaken_gps = (RET_RTCunixtime - gpsStartTime);
        if (modem.registerOnNetwork(DEFAULT_CONNECTION_ATTEMPTS,RET_setting_connection_timeout,RET_NetworkFailCount)) {
            int timetaken_total = (RET_RTCunixtime - GLOBAL_wakeTime);
            int timetaken_connection = (RET_RTCunixtime - connectionStartTime);
            char bytestosend[160];
            if (turnon) {
                snprintf(bytestosend,sizeof(bytestosend),"(%s,a:log,f:%d,t:%.1f,v:%.2f,z:TURNON,e:%d,y:%d,x:%d,c:1,s:1%s)\0",GLOBAL_defaultApi,FW_VERSION,RET_temperature,RET_voltage,timetaken_total,timetaken_connection,timetaken_gps,locString);
            } else {
                snprintf(bytestosend,sizeof(bytestosend),"(%s,a:log,f:%d,t:%.1f,v:%.2f,z:TURNOFF,e:%d,y:%d,x:%d,c:1,s:1%s)\0",GLOBAL_defaultApi,FW_VERSION,RET_temperature,RET_voltage,timetaken_total,timetaken_connection,timetaken_gps,locString);
            }
            char result[180];
            snprintf(result,sizeof(result),"%s",modem.USSDmessage(bytestosend, false, GLOBAL_defaultApi));
        }
    }
}
void event_location_tx(bool failsafe) {
    //check if we have any outstanding to send
    failed_broadcasts_tx();
    bool selfTestResult = selfTest();
    char bytesToSend[160];
    //see if we are in range of ble detector, if so, no need to get gps and broadcast
    //Set any network or GPS fail flags
    if (RET_NetworkFailCount > DEFAULT_MAX_FAILED_CONNECTIONS) addToExceptionString("NF");
    if (RET_GPSFailCount > DEFAULT_MAX_FAILED_GPS) addToExceptionString("GF");
    //lets check to see if we've had repeated comms failings in this location, if so dont bother trying. reset by movment and timeout
    if (RET_NetworkFailCount <= DEFAULT_MAX_FAILED_CONNECTIONS) {
        if (RET_receivedNewSettings) {RET_receivedNewSettings = false; addToExceptionString("SR"); }
        if (modem.on(RET_force2G)) {
            char locString[70];
            int gpsStartTime = RET_RTCunixtime;
            memcpy(locString, modem.getLocation(RET_setting_location_accuracy, RET_setting_location_timeout, RET_GPSFailCount, RET_NetworkFailCount), sizeof(locString));
            int timetaken_gps = (RET_RTCunixtime - gpsStartTime);
            
            //SEND DATA
            int connectionStartTime = RET_RTCunixtime;
            if (modem.registerOnNetwork(DEFAULT_CONNECTION_ATTEMPTS,RET_setting_connection_timeout,RET_NetworkFailCount)) {
                int timetaken_total = (RET_RTCunixtime - GLOBAL_wakeTime) + 10; //add 10 for avg ussd response time.
                int timetaken_connection = (RET_RTCunixtime - connectionStartTime);
                //Check if we should wait for settings back
                bool getSettings = true;
                //work out if we need to get settings back
                if (  ((RET_RTCunixtime - RET_SettingsGotAt)/3600) < RET_setting_minimumupdate_hrs  ) { getSettings = false; timetaken_total -= 10;} //remove the extra 10 seconds from times
                snprintf(bytesToSend,sizeof(bytesToSend),"(%s,a:loc,f:%d,t:%.1f,v:%.2f,z:LOC-%s,e:%d,y:%d,x:%d,j:%d,c:1,s:%d%s)\0",GLOBAL_defaultApi,FW_VERSION,RET_temperature,RET_voltage,GLOBAL_exceptionString,timetaken_total,timetaken_connection,timetaken_gps,RET_motionTriggeredInLocTXInterval,getSettings,locString);
                char result[180];
                snprintf(result,sizeof(result),"%s",modem.USSDmessage(bytesToSend, getSettings, GLOBAL_defaultApi));
                if (getSettings == true && strcmp(result, "sendokrecfail") == 0) {
                    //send ok but receive fail
                    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"USSD Send Ok but Rec fail");debug_exe();}
                    sendErrorLog("ERR-LOC-NR");
                } else if (strcmp(result, "sendok") == 0) {
                    //do nothing
                } else if (strcmp(result, "sendfail") == 0) {
                    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"USSD Send Fail");debug_exe();}
                    //need to log for retry
                } else {
                    if (getSettings) {
                        if (saveSettings(result) == false){sendErrorLog("ERR-LOC-IR");}
                    }
                }
            }
        }
    }
    //RESETS
    RET_motionTriggeredInLocTXInterval = false;
    setEventTime_Location();
}
void event_activity_tx() {
    //check if we have any outstanding to send
    failed_broadcasts_tx();
    //check we have something to send...
    bool sendok = false;
    if (RET_motionTotalActivityHoursSincePost > 0.0f && RET_setting_activity_mode > 0) {
        //Build data to send
        char bytesToSend[160];
        if (RET_setting_activity_mode == 1) {
            snprintf(bytesToSend,sizeof(bytesToSend),"(%s,a:act,t:%u,r:%.2f,h:%.2f,c:1)\0",GLOBAL_defaultApi,RET_motionFrameStart,RET_motionTotalActivityHoursSincePost,RET_motionTotalActivityHours);
        } else if (RET_setting_activity_mode == 2) {
            snprintf(bytesToSend,sizeof(bytesToSend),"(%s,a:act,e:%s,t:%u,r:%.2f,c:1)\0",GLOBAL_defaultApi,RET_activityData,RET_motionFrameStart,RET_motionTotalActivityHoursSincePost);
        }
        if (RET_NetworkFailCount <= DEFAULT_MAX_FAILED_CONNECTIONS) {
            if (modem.on(RET_force2G)) {
                //SEND DATA
                if (modem.registerOnNetwork(2,RET_setting_connection_timeout,RET_NetworkFailCount)) {
                    char result[100];
                    bool getResponse = false;
                    snprintf(result,sizeof(result),"%s",modem.USSDmessage(bytesToSend, getResponse, GLOBAL_defaultApi));
                    if (strcmp(result, "sendok") == 0) {
                        sendok = true;
                        //if(!RET_debug) {sendok = true;} //FOR TESTING
                    }
                }
            }
        }
        //If send failed then log it for retry - only need to do this for mode 2, for mode the time just increments
        if (sendok == true) {
            if (RET_setting_activity_mode == 1) {
                RET_motionTotalActivityHoursSincePost = 0.0f;
                if (RET_haveSettings) { RET_motionFrameStart = RET_RTCunixtime; }
            } else if (RET_setting_activity_mode == 2) {
                RET_motionTotalActivityHoursSincePost = 0.0f;
                if (RET_haveSettings) { RET_motionFrameStart = RET_RTCunixtime; }
            }
        } else if (sendok == false) {
            if (RET_setting_activity_mode == 1) {
                //do nothing
            } else if (RET_setting_activity_mode == 2) {
                failed_broadcast_log(bytesToSend);
                RET_motionTotalActivityHoursSincePost = 0.0f;
                if (RET_haveSettings) { RET_motionFrameStart = RET_RTCunixtime; }
            }
        }
    }
    //RESETS
    memset(RET_activityData,0x00,sizeof(RET_activityData));
    setEventTime_Activity();
}
/*
void event_activity_tx() {
    bool sendok = false;
    //check if we have any outstanding to send
    failed_broadcasts_tx();
    
    //check we have something to send...
    if (RET_motionTotalActivityHoursSincePost > 0.0f && RET_setting_activity_mode > 0) {
        //Build data to send
        char bytesToSend[160];
        if (RET_setting_activity_mode == 1) {
            snprintf(bytesToSend,sizeof(bytesToSend),"(%s,a:act,t:%u,r:%.2f,h:%.2f,c:1)\0",GLOBAL_defaultApi,RET_motionFrameStart,RET_motionTotalActivityHoursSincePost,RET_motionTotalActivityHours);
        } else if (RET_setting_activity_mode == 2) {
            snprintf(bytesToSend,sizeof(bytesToSend),"(%s,a:act,e:%s,t:%u,r:%.2f,c:1)\0",GLOBAL_defaultApi,RET_activityData,RET_motionFrameStart,RET_motionTotalActivityHoursSincePost);
        }
        if (RET_NetworkFailCount <= DEFAULT_MAX_FAILED_CONNECTIONS) {
            if (modem.on(RET_force2G)) {
                //SEND DATA
                if (modem.registerOnNetwork(2,RET_setting_connection_timeout,RET_NetworkFailCount)) {
                    char result[100];
                    bool getResponse = false;
                    snprintf(result,sizeof(result),"%s",modem.USSDmessage(bytesToSend, getResponse, GLOBAL_defaultApi));
                    if (strcmp(result, "sendok") == 0) {
                        //sendok = true;
                        if(!RET_debug) {sendok = true;}
                    }
                }
            }
            modem.off(true);
        }
        //If send failed then log it for retry
        if (sendok == false) {
            failed_broadcast_log(bytesToSend);
        }
    }
    //RESETS
    RET_motionTotalActivityHoursSincePost = 0.0f;
    memset(RET_activityData,0x00,sizeof(RET_activityData));
    if (RET_haveSettings) { RET_motionFrameStart = RET_RTCunixtime; } //this is now safe to do as we are logging the fails
    setEventTime_Activity();
}
*/

//------------------------------------------------------------------------------
// STATE ENGINE
//------------------------------------------------------------------------------ 
void mainStateEngine() {
    switch(RET_state) {
        case STATE_SETUP :
        { //need the curlies to avoid "transfer of control bypass init error warning"
            RET_busy = true;
            //check that we havent run setup too recently
            time_t setupRunAt_delta = (RET_RTCunixtime - RET_SetupRunAt);
            if (RET_SetupRunAt == 0 || setupRunAt_delta >= ONEDAYINSECONDS) {
                updateBatteryV();
                if (event_setup(false)) {
                    // All good
                    setState(STATE_NORMAL);
                } else {
                    RET_eventTime_wakeFromDormant = (RET_RTCunixtime + ONEDAYINSECONDS);
                    setState(STATE_DORMANT);
                    if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"SETUP FAILED:DORMANT until %u\n",RET_eventTime_wakeFromDormant);debug_exe();}
                }
            } else {
                time_t setupCanRunAt = (RET_RTCunixtime + (ONEDAYINSECONDS - setupRunAt_delta));
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"SETUP CAN RUN AGAIN @%u \n",setupCanRunAt);debug_exe();}
            }
            RET_busy = false;
            break;
        }
        case STATE_NORMAL :
        {   
            RET_busy = true;
            //LOCATION EVENT
            bool run_location_tx = false;
            switch (RET_setting_location_mode) {
                case 1: //INTERVAL POST
                    if(RET_RTCunixtime >= RET_eventTime_location_tx && RET_eventTime_location_tx > 0) { run_location_tx = true; }
                    break;
                case 2: //INTERVAL POST WITH MOTION CHECK
                    if(RET_RTCunixtime >= RET_eventTime_location_tx && RET_eventTime_location_tx > 0) { 
                        if (RET_motionTriggeredInLocTXInterval == true) {
                            run_location_tx = true;
                        } else {
                             if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "Skip TX no mot\n");debug_exe();}
                        }
                    }
                    break;
                case 3: //POST ON STOP MOTION
                    if (GLOBAL_motionStopFlagTriggered) { run_location_tx = true; GLOBAL_motionStopFlagTriggered = false; }
                    break;
            }
            if(RET_RTCunixtime >= RET_eventTime_location_failsafe_tx && RET_eventTime_location_failsafe_tx > 0) { 
                RET_NetworkFailCount = 0; //reset to ensure connection
                RET_GPSFailCount = 0; // reset to ensure gps try
                updateBatteryV();
                event_location_tx(true);
            } else {
                if (run_location_tx) { updateBatteryV(); event_location_tx(false); }
            }
            
            
            //ACTIVITY EVENT
            bool run_activity_tx = false;
            if(RET_RTCunixtime >= RET_eventTime_activity_tx && RET_eventTime_activity_tx > 0) {
                if (RET_setting_activity_mode == 1 && RET_motionTotalActivityHoursSincePost > 0.0f) {
                    run_activity_tx = true;
                }
                if (RET_setting_activity_mode == 2 && strlen(RET_activityData) > 1) {
                    run_activity_tx = true;
                }
            }
            //If in sendevent mode, check if buffer is close to full, if so send
            if (RET_setting_activity_mode == 2 && strlen(RET_activityData) > (ACTIVITY_BUFFERSIZE-20)) { run_activity_tx = true; }
            if (run_activity_tx) { event_activity_tx(); }
            
            RET_busy = false;
            break;
        }
        case STATE_DORMANT :
        {
            RET_busy = true;
            if (RET_RTCunixtime >= RET_eventTime_wakeFromDormant) {
                if (RET_haveSettings) {
                    setState(STATE_NORMAL); 
                } else {
                    setState(STATE_SETUP); 
                }
            }
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"STATE:DORMANT until %u  Prev S:%d\n",RET_eventTime_wakeFromDormant,RET_state_prev);debug_exe();}
            RET_busy = false;
            break;
        }
        case STATE_BUTTONPRESS1 :
        {
            RET_busy = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"BUTTON PRESS 1\n");debug_exe();}
            updateBatteryV();
            if (RET_voltage < 2.5f) {
                LED1blink(3,500);
            } else if (selfTest() == false){
                LED1blink(4,500);
            } else {
                if(RET_state_prev == STATE_DORMANT) {
                    LED1on(500);
                } else if (RET_state_prev == STATE_NORMAL) {
                    LED1blink(2,500);
                }
            }
            if (RET_state_prev != STATE_NORMAL || RET_state_prev != STATE_DORMANT) {
                setState(STATE_NORMAL);
            } else {
                setState(RET_state_prev);
            }
            dumpSettings();
            RET_busy = false;
            break;
        }
        case STATE_BUTTONPRESS3 :
        {
            RET_busy = true;
            if(RET_state_prev == STATE_DORMANT || RET_state_prev == STATE_NORMAL) {
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"BUTTON PRESS 3\n");debug_exe();}
                LED1blink(3,500);
                LED1on(0);
                updateBatteryV();
                event_setup(true);
                LED1off();
            }
            setState(STATE_NORMAL); //turns device back on
            RET_busy = false;
            break;
        }
        case STATE_BUTTONPRESS5 :
        {
            RET_busy = true;
            if(RET_state_prev == STATE_DORMANT || RET_state_prev == STATE_NORMAL) {
                if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"BUTTON PRESS 5\n");debug_exe();}
                LED1blink(5,500);
                LED1on(0);
                updateBatteryV();
                event_location_tx(false);
                LED1off();
            }
            setState(STATE_NORMAL); //turns device back on
            RET_busy = false;
            break;  
        }
        case STATE_TURNOFF :
        {
            RET_busy = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"BUTTON HOLD \n");debug_exe();}
            RET_eventTime_wakeFromDormant = (RET_RTCunixtime + THREEDAYSINSECONDS);
            setState(STATE_DORMANT);
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"TURNING OFF\n");debug_exe();}
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"STATE:DORMANT until %u\n",RET_eventTime_wakeFromDormant);debug_exe();}
            LED1on(5000);
            //event_turnonofflog_tx(false);
            RET_busy = false;
            break;
        }
        case STATE_TURNON :
        {
            RET_busy = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"BUTTON HOLD \n");debug_exe();}
            if (RET_haveSettings) {
                setState(STATE_NORMAL);
            } else {
                setState(STATE_SETUP);
            }
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"TURNING ON\n");debug_exe();}
            LED1blink(20,100);
            RET_busy = false;
            break;
        }
        case STATE_SCORCHEDEARTH :
        {
            RET_busy = true;
            if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"SCORCHED EARTH - RESETTING");debug_exe();}
            setState(STATE_SETUP); //this wont be used but just incase to kick it out of this state
            read_app_data_from_flash(&app_data);
            app_data.current_firmware_version = 0;
            app_data.target_firmware_version = 0;
            write_app_data_to_flash(&app_data);
            system_reset();
            RET_busy = false;
            break;
        }
        default :
        {
            setState(STATE_SETUP);
        }
    }
}

//------------------------------------------------------------------------------
// MAIN
//------------------------------------------------------------------------------ 
int main() {
    //INIT
    LED1off();
    ThisThread::sleep_for(2000); //Initial pause, this is needed for softdevice to init, dont remove!! If we dont have this we get crashes
    watchdog.configure(300.0); //5 mins
    modem.off(false);
    RTCticker.attach(&RTCtick, 1.0);
    LPtimer.start();
    button.fall(&buttonPress);
    button.rise(&buttonRelease);
    read_app_data_from_flash(&app_data);
    dumpSettings();
    
    //CHECK IF THIS IS RESET
    //0x00000008 == lock up //0x00000004 == soft reset  //0x00000002 == watchdog  //0x00000001 == button/hardreset 
    if (NRF_POWER->RESETREAS != 0xffffffff) {
        RET_coldBoot = 1;
        switch(NRF_POWER->RESETREAS) {
            case 0x00000001  :
                //if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"RR:HW\n");debug_exe();}
            break;
            case 0x00000002  :
                //if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"RR:WD\n");debug_exe();}
            break;
            case 0x00000004  :
                //if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"RR:SW\n");debug_exe();}
            break;
            case 0x00000008  :
                //if(RET_debug){debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"RR:LU\n");debug_exe();}
            break;            
        }
        NRF_POWER->RESETREAS = 0xffffffff;
    } else {
        //if its not one of these reasons then its a power-on or a brown-out
        //if(get_flag(&app_data, app_execution_flag) == false) {
            //THIS is a unexpected reset / brownout???
        //}
    }
    
    //CHECK FOR FIRST BOOT
    if (RET_coldBoot == 1) { 
        setDefaults(); 
        //check battery
        updateBatteryV();
        if (RET_voltage < 2.5f) {
            //battery low
            LED1errorCode(10,2);
        }
        addToExceptionString("FR");
    }
    
    //MAIN LOOP
    while(true) {    
        //WATCHDOG
        watchdogKick();
        RET_asleep = false;
        updateTemperatures();
        
        //INIT
        resetGlobals();
        healthCheck(); //this must be after resetGlobals
        GLOBAL_wakeTime = RET_RTCunixtime;
        
        //check and log motion
        if (RET_state == STATE_NORMAL || RET_state == STATE_SETUP) { 
            if (checkMotion() == true) {
                //if motionstate is true, then reset network and gps fail counts, so that they are not skipped
                RET_NetworkFailCount = 0;
                RET_GPSFailCount = 0;
            }
        }
        
        //MAIN LOGIC
        if(RET_debug){
            if (RET_state != 99) {
                debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "F:%d, S:%d, SET:%d, MF:%d, MS:%d, MTX:%d, %u, L:%u, LFS:%u, A:%u", FW_VERSION, RET_state, RET_haveSettings, RET_motionTriggeredinFrame, RET_motionState, RET_motionTriggeredInLocTXInterval, RET_RTCunixtime,RET_eventTime_location_tx,RET_eventTime_location_failsafe_tx,RET_eventTime_activity_tx);debug_exe();
                debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "ACT M:%d, HsP:%.2f, Ht:%.2f, D:%s",RET_setting_activity_mode,RET_motionTotalActivityHoursSincePost,RET_motionTotalActivityHours,RET_activityData);debug_exe();
            } else {
                debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer), "F:%d, S:%d, WAKE@:%u\n", FW_VERSION, RET_state, RET_eventTime_wakeFromDormant);debug_exe();
            }
            if (RET_coldBoot == false && RET_RTCunixtime > RET_debug_offat) {
                debug_prep();snprintf(GLOBAL_debug_buffer, sizeof(GLOBAL_debug_buffer),"DEBUGGING OFF\n");debug_exe();
                RET_debug = false;
            }
        }

        //STATE ENGINE
        mainStateEngine();
        
        //LOG FIRST RUN - BOOTLOADER COMMS
        if (RET_coldBoot) {
            recordFirmwareAsValid();
            RET_coldBoot = 0;
        }
        
        //PRE-SLEEP ACTIONS
        LED1off();
        if (GLOBAL_modemOn) {
            modem.off(true); //SOFT SHUT DOWN - THIS IS THE ONLY MODEM OFF, DONT REMOVE
        } else {
            modem.off(false); //HARD SHUT DOWN - THIS IS THE ONLY MODEM OFF, DONT REMOVE
        }
        RET_motionTriggeredinFrame = false;
        if (GLOBAL_needToConfigureLis3dh) { lis3dh_configureForSleep(RET_setting_motion_g,RET_setting_impact_g); }
        nrf_configureForSleep();
        NRFuart_uninit();
        watchdogKick();
        
        //SLEEP
        RET_asleep = true;
        mainthread.wait(DEFAULT_SLEEP_FRAME);
    }
}