Senet network coverage survey tool

Dependencies:   GeoPosition Senet_Packet lib_gps lib_mma8451q lib_mpl3115a2 lib_sx9500 lmic_MOTE_L152RC mbed-src

Senet Network Coverage Program

Test Senet Network coverage with various data rates and transmit powers

/media/uploads/shaunkrnelson/norammote.jpg

Configure Device ID and App Key

For your device to connect to the Senet Network, set Device ID and App Key in Commissioning.h

Commissioning.h

/* CHANGE: Device ID  registered to your Developer Portal account */
 u1_t reverse_DEVEUI[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 

/* CHANGE: Device App Key. To get this select the device, click on Gear button at top left then select Device Edit */  
u1_t DEVKEY[16]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
                                                                        
/*NOCHANGE: Senet Developer Portal Application ID */ 
 u1_t reverse_APPEUI[8]={0x00,0x25,0x0C,0x00,0x00,0x01,0x00,0x01};
 

Connecting to the Senet Network

When the device is turned on the USR LED will blink while it is joining the Senet Network. When LED state changes to solid green it has joined the network. Failure to join network will be the result of:

  1. No network coverage in that area
  2. App Key configuration is incorrect

Siting Mode

The device is in Siting test mode when positioned vertically with LEDs facing up. The Siting test consists of transmitting Senet GPS packets with acknowledgment requested at transmit power levels 14, 20, 30 in that order. For each power level the device will attempt 3 transmits at DR0, stopping if ack is received and moving on to next tx power. After first run through all powers at DR0 the test will stop if acknowledgements received at all power levels; otherwise, the test will proceed to alternate between 3 transmits at DR4 and 3 at DR0 for remaining unacknowledged tx powers until ack received.

Siting LED Profile

  1. USR
    1. Blink fast while joining
    2. Solid when joined and GPS lock
    3. Blink slow when not GPS lock
  1. RED
    1. 30 dBm status indicator
    2. Blink on transmit
    3. Solid after downlink received
  1. YELLOW
    1. 20 dBm status indicator
    2. Blink on transmit
    3. Solid after ack received
  1. GREEN
    1. 14 dBm status indicator
    2. Blink on transmit
    3. Solid after downlink received

Range Mode

The device is in this mode when not in the siting orientation.
This test consists of transmitting 8 packets at:

  1. DR3 Tx Power 14
  2. DR0 Tx Power 14
  3. DR4 Tx Power 20
  4. DR3 Tx Power 20
  5. DR0 Tx Power 20
  6. DR4 Tx Power 26
  7. DR3 Tx Power 30
  8. DR0 Tx Power 30

After transmit cycle completes the next test will start in 560 seconds unless following:

  1. After 140 seconds if no ack received
  2. After 80 seconds if 30 meters travelled from last transmit location

Range LED Profile

  • USR
    • Blink fast while joining
    • Solid when joined and GPS lock
    • Blink slow when not GPS locked
  • RED
    • Transmit indicator
    • Blink on transmit
  • YELLOW
    • Ack indicator
    • Set at beginning of transmit cycle and cleared when ack received
  • GREEN
    • V3 Mote
      • Test indicator
      • Solid while test transmit cycle in progress
    • V2 Mote
      • Not controllable by firmware

Switching Mode

Set mote orientation to desired mode. After a few seconds the new orientation will be detected and the device will indicate it is switching mode by blinking the red, yellow and green LEDs simultaneously for a couple seconds before switching to the new mode.

main.cpp

Committer:
shaunkrnelson
Date:
2017-01-20
Revision:
19:e37b3bd60d5a
Parent:
16:2e5438977b6c

File content as of revision 19:e37b3bd60d5a:

#include "mbed.h"
#include "lmic.h"
#include "debug.h"
#include "gps.h"
#include "mpl3115a2.h"
#include "mma8451q.h"
#include "sx9500.h"
#include "GeoPosition.h"
#include "senet_packet.h"

// Automatically generated by the program_device script
#include "Commissioning.h"
#define NORAM_MOTE_DEVICE_TYPE 0x001001 
#define MAJOR          1
#define MINOR          1
#define POINT          4
#define BUILD_NUMBER   0
#define DEVELOPER_ID   0



// provide application router ID (8 bytes, LSBF)
void os_getArtEui (u1_t* buf) {
    //memcpy(buf, APPEUI, 8);
    LMIC_reverse_memcpy(buf, reverse_APPEUI, 8);
}

// provide device ID (8 bytes, LSBF)
void os_getDevEui (u1_t* buf) {
    //memcpy(buf, DEVEUI, 8);
    LMIC_reverse_memcpy(buf, reverse_DEVEUI, 8);
}

// provide device key (16 bytes)
void os_getDevKey (u1_t* buf) {
    memcpy(buf, DEVKEY, 16);
}

typedef enum {
    MOTE_NONE = 0,
    MOTE_V2,
    MOTE_V3
} mote_version_e;

/***********************************************************************
 * END LORA Device configuration
 ***********************************************************************/

// Following is peripheral configuration
GPS gps(PB_6, PB_7, PB_11);
GeoPosition lastTxPos;

// I2C Bus connecting sensors 
I2C i2c(I2C_SDA, I2C_SCL);
DigitalIn i2c_int_pin(PB_4);

// Accelerometer
MMA8451Q  mma8451q(i2c, i2c_int_pin);
// Altimeter 
MPL3115A2 mpl3115a2(i2c, i2c_int_pin);
// Proximity sensor
SX9500 sx9500(i2c, PA_9, PA_10);

// Applicative transmit datarate
u1_t dr = 0;
// Applicative transmit power
u1_t pw = 0;

// LEDs
#define LED_RED         PB_1
#define LED_YEL         PB_10
#define LED_GREEN       PC_3
#define LED_USR         PA_5

DigitalOut ledRed(LED_RED);
DigitalOut ledYellow(LED_YEL);
DigitalOut ledGreen(LED_GREEN);
DigitalOut ledUsr(LED_USR);

DigitalOut pc_7(PC_7);
DigitalIn pc_1(PC_1);
AnalogIn *bat;

uint8_t ReadBatteryLevel()
{
    uint8_t level = 0;

    if(bat != NULL)
        level = ((bat->read_u16() >> 8) + (bat->read_u16() >> 9))>>5;

    debug("Battery level: %d\r\n", level);
    return level;
}

// Get Mote version
mote_version_e get_mote_version()
{ 
    static mote_version_e mote_version = MOTE_NONE;

    if (mote_version == MOTE_NONE)
    {
        pc_7 = 1;
        char first = pc_1;
        pc_7 = 0;

        if (first && !pc_1) {
            printf("v2\r\n");
            mote_version = MOTE_V2;
            bat = new AnalogIn(PA_0);
        } else {
            printf("v3\r\n");
            mote_version = MOTE_V3;
            bat = new AnalogIn(PA_1);
        }
    }
    return mote_version;
}

void board_peripherals_init()
{
    ledRed    = 1;
    ledYellow = 1;
    ledGreen  = 1;
    ledUsr    = 0;

    // Start GPS
    gps.init();
    if(get_mote_version() == MOTE_V3) 
        gps.en_invert = false;
    else
        gps.en_invert = true;

    gps.enable(false);

    GPIOA->MODER |= 0x01415500;     // unused pins as outputs: PA4, PA5, PA6, PA7, PA8, (PA11,PA12 USB)
    //printf("GPIOA->MODER:%08x\r\n", GPIOA->MODER);

    GPIOB->MODER |= 0x00000401;     // unused pins as outputs: PB0(HDR_DIO1), PB5 (PB10 pulled hi by LED), PB3-T_SWO
    //printf("GPIOB->MODER:%08x\r\n", GPIOB->MODER);

    GPIOC->MODER |= 0x00000045;    // unused pins as outputs: PC0(hdr_fem_csd) PC1(hdr_fem_ctx) PC3(SPI3_enable)
    //printf("GPIOC->MODER:%08x\r\n", GPIOC->MODER);

    sx9500.RegProxCtrl0.bits.sensor_en = 3; // only CS0 and CS1 connected
    sx9500.write(SX9500_REG_PROXCTRL0, sx9500.RegProxCtrl0.octet);
    
    // set PROXTHRESH to 80 because CS1 has 48 showing always on PROXDIFF
    sx9500.write(SX9500_REG_PROXCTRL6, 0x04);
    
    // Enable accelerometer orientation detection 
#define DEBUG
#ifdef DEBUG
    mma8451q.verbose = true;
#endif
    mma8451q.orient_detect();
    mma8451q.set_active(true);

    mpl3115a2.init();
    mpl3115a2.SetModeStandby();

    gps.enable(true);
}

// application commmon
bool gps_service()
{
    gps.have_fix = false;
    if (gps.enabled()) {
        for(int32_t i = 0; i < 10; i++)
        {
            gps.service();
            if (gps.have_fix == true) { 
                return true;
            }
            else
                wait(.1);
        }
    } 
    return false;
}



static void restore_hsi()
{
    RCC_OscInitTypeDef osc_init;
    /* if HSI was shut off in deep sleep (needed for AnalogIn) */
    HAL_RCC_GetOscConfig(&osc_init);
    if (osc_init.HSIState != RCC_HSI_ON) {
        // Enable the HSI (to clock the ADC)
        osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSI;
        osc_init.HSIState       = RCC_HSI_ON;
        osc_init.PLL.PLLState   = RCC_PLL_NONE;
        HAL_RCC_OscConfig(&osc_init);    
    }    
}


u1_t PrepareDataFrame( SenetPacketT type )
{
    u1_t pktLen = 0;

    restore_hsi();

    switch (type)
    {
        case SELF_ID_PACKET:
        {
            // Self Id packet type serialized to the LMIC frame buffer
            SelfIdPacket packet(LMIC.frame, MAX_LEN_FRAME);
           
            // Device Type
            packet.setDeviceType(NORAM_MOTE_DEVICE_TYPE, get_mote_version());

            // Software version
            packet.setSwVersion(MAJOR, MINOR, POINT, BUILD_NUMBER, DEVELOPER_ID);

            // Battery level
            packet.setBatteryLevel(ReadBatteryLevel());

            // Serialize  packet to LMIC transmit buffer
            pktLen = packet.serialize();

            break;
        }
        case GPS_PACKET:
        default:
        { 
            // Gps packet type serialized to the LMIC frame buffer
            GpsPacket packet(LMIC.frame, MAX_LEN_FRAME);
            
            // set packet transmit power
            packet.setTxPower(pw);

            // Set packet coordinates 
            uint16_t altitudeGps = atoi(gps.NmeaGpsData.NmeaAltitude);
            packet.setCoordinates(gps.LatitudeBinary, gps.LongitudeBinary, altitudeGps); 
            lastTxPos.set(gps.Latitude,gps.Longitude);
           
            // Serialize  packet 
            pktLen = packet.serialize();
            break;
        }
    }

    return pktLen;
}

void displayBuildInfo()
{
    debug("Range & Site Tester\r\n");
    debug("Version %02d.%02d.%02d.%04d.%02d\r\n",MAJOR,MINOR,POINT,BUILD_NUMBER,DEVELOPER_ID);
    debug("\r\nDevEUI: %02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x\r\n", 
            reverse_DEVEUI[0],
            reverse_DEVEUI[1],
            reverse_DEVEUI[2],
            reverse_DEVEUI[3],
            reverse_DEVEUI[4],
            reverse_DEVEUI[5],
            reverse_DEVEUI[6],
            reverse_DEVEUI[7]);
    debug("AppEUI: %02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x\r\n", 
            reverse_APPEUI[0],
            reverse_APPEUI[1],
            reverse_APPEUI[2],
            reverse_APPEUI[3],
            reverse_APPEUI[4],
            reverse_APPEUI[5],
            reverse_APPEUI[6],
            reverse_APPEUI[7]);
}


void startLMIC() 
{
    // reset MAC state
    LMIC_reset(); 
    // start joining
    LMIC_startJoining();
}



// Operational mode
enum OperMode
{
    // Range testing
    RANGE_TEST, 
    // Site testing
    SITE_TEST,
    // Sending self-id packet
    SELF_ID,
    // No mode set 
    IDLE
};

// OS job management objects
static osjob_t statusLedJob; 
static osjob_t txLedJob;
static osjob_t orientationJob;
static osjob_t sendFrameJob;

// Mote orientation is checked periodically. The mote test mode 
// is selected by orientation. When mote is vertical it is in 
// site test mode and range test mode when horizontal. 
// Note that changing mote mode will abort previous mode testing
#define ORIENTATION_CHECK_PERIOD  5 

// Debounce orientation change 
#define ORIENTATION_DEBOUNCE_TIME 5

#define STATUS_FAST_BLINK   1    
#define STATUS_SLOW_BLINK   4    
#define STATUS_OK_PERIOD    5

static u4_t     txCount       = 0;
static ostime_t lastTestDoneT = 0;
static bool     ackRequested  = false;
static OperMode operMode      = IDLE;
static OperMode txMode        = IDLE;

//////////////////////////////////////////////////
// Range test  configuration
//////////////////////////////////////////////////
#define RANGE_TXCYCLE_TRANSMITS    8
#define RANGE_NEXT_TEST_LONG       560    
#define RANGE_NEXT_TEST_NOACK      140    
#define RANGE_NEXT_TEST_DISTANCE   80
#define RANGE_NEXT_TEST_MIN_TIME   80
#define RANGE_NEXT_TX              5    
#define RANGE_START_TEST_DISTANCE  30 // meters
#define RANGE_NEXT_TEST_DISTANCE_T 15 


//////////////////////////////////////////////////
// Site test configuration 
//////////////////////////////////////////////////
#define SITE_TX_PW1 14
#define SITE_TX_PW2 20 
#define SITE_TX_PW3 30 
#define SITE_GREEN_LED_PW  SITE_TX_PW1
#define SITE_YELLOW_LED_PW SITE_TX_PW2
#define SITE_RED_LED_PW    SITE_TX_PW3

static u1_t siteDrs[2] = {0,4};
static u1_t sitePws[3] = {SITE_TX_PW1, SITE_TX_PW2, SITE_TX_PW3};
static u1_t siteAck[3] = {0,0,0};
static u1_t siteDr = 0;
static u1_t sitePw = 0;
static u1_t siteDrPwAttempts = 0;
#define SITE_DR_MAX sizeof(siteDrs)
#define SITE_PW_MAX sizeof(sitePws)
#define SITE_PW_DR_ATTEMPTS 3


static void txLedCb(osjob_t *job)
{
    if(operMode == RANGE_TEST)
    {
        ledRed = 1;
    }
    else if(operMode == SITE_TEST)
    {
        switch(pw)
        {
            case SITE_GREEN_LED_PW:  
                ledGreen = 1;
                break;
            case SITE_YELLOW_LED_PW:  
                ledYellow = 1;
                break;
            case SITE_RED_LED_PW:  
                ledRed = 1;
                break;
        }
    }
}


static void onSendFrame(osjob_t* j)
{
    u1_t pktSize = 0;

    // Return if still transmitting. Next transmit will be scheduled by onEvent() when tx finishes  
    if (LMIC.opmode & OP_TXRXPEND) 
        return;

    if(operMode == RANGE_TEST)
    {
        // Select dr and tx power  
        switch(txCount&0x07)
        {
            case 0:
                dr = 3;
                pw = 14;
                ackRequested = true; // set this on the first tx of the group 
                ledYellow = 0;
                ledGreen  = 0;
                break;
            case 1:
                dr = 0;
                pw = 14;
                break;
            case 2:
                dr = 4;
                pw = 20;
                break;
            case 3:
                dr = 3;
                pw = 20;
                break;
            case 4:
                dr = 0;
                pw = 20;
                break;
            case 5:
                dr = 4;
                pw = 26;
                break;
            case 6:
                dr = 3;
                pw = 30;
                break;
            case 7:
                dr = 0;
                pw = 30;
                break;
        }
        // Blink transmit LED
        ledRed = 0;
        os_setTimedCallback( &txLedJob, os_getTime() + 1, txLedCb); 
    }
    else if (operMode == SITE_TEST) 
    {
        ackRequested = true;

        if((siteDr < SITE_DR_MAX) && (sitePw < SITE_PW_MAX))
        {
            dr = siteDrs[siteDr];
            pw = sitePws[sitePw];
        }
        // Restarting test
        else
        {
            debug("Bad dr=%d or pw=%d index\r\n", siteDr, sitePw); 
            siteDr = 0;
            sitePw = 0;
        }
        // Blink tx LED
        switch(pw)
        {
            case SITE_GREEN_LED_PW:  
                ledGreen = 0;
                break;
            case SITE_YELLOW_LED_PW:  
                ledYellow = 0;
                break;
            case SITE_RED_LED_PW:  
                ledRed = 0;
                break;
        }
        os_setTimedCallback( &txLedJob, os_getTime() + 1, txLedCb); 
    }

    // Send GPS data 
    for(int32_t i=0; i<3; i++)
    {
        if(gps_service() == true)
        {
            pktSize = PrepareDataFrame(GPS_PACKET); 
            break;
        }
    }
    
    // if unable to get GPS, send self-id
    if(0 == pktSize)
        pktSize = PrepareDataFrame(SELF_ID_PACKET); 
   

    // Set next dr and tx power 
    LMIC_setDrTxpow(dr,pw);      

    txCount++;
    txMode = operMode;

    debug("onSendFrame mode=%d: dr=%d, pw=%d, ack=%d\r\n",operMode, dr, pw, ackRequested);
    LMIC_setTxData2(1, LMIC.frame, pktSize, ackRequested); 
}

static void checkIfRangeTestStart(osjob_t *job)
{ 
    const ostime_t waitT   = ackRequested ? RANGE_NEXT_TEST_NOACK : RANGE_NEXT_TEST_LONG;
    const ostime_t startT  = lastTestDoneT + sec2osticks(waitT); 

    debug("%u seconds elapsed since last test\r\n",osticks2ms(os_getTime()-lastTestDoneT)/1000);

    if(os_getTime() >= startT)
    {
        os_setCallback( &sendFrameJob, onSendFrame ); 
    }
    else if(gps_service())
    {
        // Do we have last tx coordinates 
        if((lastTxPos.latitude() == 0) && (lastTxPos.longitude() == 0))
        {
            // Set last tx position to current location 
            lastTxPos.set(gps.Latitude,gps.Longitude);
        }
        else
        {
            GeoPosition location(gps.Latitude, gps.Longitude); 
            double distance = location.distance(lastTxPos);

            if(distance >= RANGE_START_TEST_DISTANCE)
            {
                debug("position change %f >= %u\r\n",distance, RANGE_START_TEST_DISTANCE);
                os_setCallback( &sendFrameJob, onSendFrame ); 
                return;
            }
        }
    }
    
    // Schedule next check 
    ostime_t nextChkT = os_getTime() + sec2osticks(RANGE_NEXT_TEST_DISTANCE_T);

    // If distance check time less than test start, schedule distance check
    if(nextChkT <= startT)
    {
        os_setTimedCallback( &sendFrameJob, nextChkT, checkIfRangeTestStart); 
    }
    // Else test start less than distance check time
    else
    {
        os_setTimedCallback( &sendFrameJob, startT, onSendFrame); 
    } 
}

static void preTestStart(osjob_t *job)
{
    static u1_t wait = 0;

    // onEvent will initiate test on join or tx complete
   if(((LMIC.opmode & OP_JOINING) != 0) || ((LMIC.opmode & OP_TXRXPEND) != 0))
       return;

    if(wait < 6)
    {
        if((wait & 1) == 0)
        {
            ledRed    = 0;
            ledGreen  = 0;
            ledYellow = 0; 
        }
        else
        {
            ledRed    = 1;
            ledGreen  = 1;
            ledYellow = 1;
        }
        wait++;
        os_setTimedCallback( &sendFrameJob, os_getTime() + sec2osticks(1), preTestStart);
    }
    else
    { 
        wait = 0;
        os_setCallback( &sendFrameJob, onSendFrame);
    }
}


// Protocol event handler
void onEvent (ev_t ev) 
{
    debug_event(ev);
    switch(ev) 
    {
    // network joined, session established
    case EV_JOINED:
    { 
        displayBuildInfo();
        //
        // Disable link check validation
        LMIC_setLinkCheckMode(false);

        // Start transmitting
        os_setCallback( &sendFrameJob, onSendFrame);

        break;
    }
    // scheduled data sent (optionally data received)
    case EV_TXCOMPLETE:
        // Check transmitted frame mode matches running mode. If not, skip results
        // and start sending frames for current mode
        if(txMode != operMode)
        {
            // Prepare to start test 
            os_setCallback( &sendFrameJob, preTestStart);
        }
        else if(operMode == RANGE_TEST)
        { 
            // Check ack received
            if((LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2) )!= 0 )
            {
                ackRequested = false; 
                ledYellow = 1;
            } 

            if(0 == (txCount & 0x07))
            {
                // Schedule job to check for range test restart 
                lastTestDoneT = os_getTime();
                os_setTimedCallback( &sendFrameJob, lastTestDoneT + sec2osticks(RANGE_NEXT_TEST_MIN_TIME), checkIfRangeTestStart);
                debug("Check for test restart in %u seconds\r\n",RANGE_NEXT_TEST_MIN_TIME);

                // Turn off transmit cycle indicator led
                ledGreen  = 1;
            } 
            else
            {
                os_setTimedCallback( &sendFrameJob, os_getTime() + sec2osticks(RANGE_NEXT_TX), onSendFrame); 
            }
        }
        else if(operMode == SITE_TEST)
        {
            // Ack received
            if(((LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2) ) != 0 ))
            {
                siteAck[sitePw] = 1;

                // Turn on LED 
                switch(pw)
                {
                    case SITE_GREEN_LED_PW:
                        ledGreen = 0;
                        break;
                    case  SITE_YELLOW_LED_PW:
                        ledYellow = 0;
                        break;
                    case  SITE_RED_LED_PW:
                        ledRed = 0;
                        break;
                }
                
                // Set next tx power first transmit rate
                sitePw++;
                siteDrPwAttempts = 0;
            } 
            // No ACK
            else if(++siteDrPwAttempts >= SITE_PW_DR_ATTEMPTS)
            {
                sitePw++;
                siteDrPwAttempts = 0; 
            }

            if(sitePw >= SITE_PW_MAX)  
            {
                sitePw=0;
                siteDr++;
                if(siteDr >= SITE_DR_MAX)
                    siteDr=0;
            }

            if(siteAck[sitePw] == 0)
            {
                os_setTimedCallback( &sendFrameJob, os_getTime() + sec2osticks(1), onSendFrame ); 
            }
            else
            {
                bool foundNACK = false;

                for(uint32_t i=sitePw; i < SITE_PW_MAX;i++)
                {
                    if(siteAck[i] == 0)
                    {
                        sitePw = i;
                        os_setTimedCallback( &sendFrameJob, os_getTime() + sec2osticks(1), onSendFrame ); 
                        foundNACK = true;
                        break;
                    }
                }

                if(foundNACK == false)
                {
                    siteDr++;
                    if(siteDr >= SITE_DR_MAX) 
                        siteDr=0;

                    for(uint32_t i=0; i < sitePw;i++)
                    {
                        if(siteAck[i] == 0)
                        {
                            sitePw = i;
                            os_setTimedCallback( &sendFrameJob, os_getTime() + sec2osticks(1), onSendFrame ); 
                            break;
                        }
                    }
                }
            }
        }
        break;
    default:
        break;
    }
}


static void statusLedCb(osjob_t *job)
{
    ostime_t sleep;

    if(ledUsr == 1)
    { 
        if((LMIC.opmode & OP_JOINING) != 0) 
        {
            ledUsr = 0;
            sleep  = STATUS_FAST_BLINK;
        }
        else if(gps.have_fix == false)
        {
            ledUsr = 0;
            sleep  = STATUS_SLOW_BLINK;
        }
        else
        {
            sleep = STATUS_OK_PERIOD;
        }
    }
    else
    {
        ledUsr = 1;
        sleep  = 1;
    }

    os_setTimedCallback( &statusLedJob, os_getTime() + sec2osticks(sleep), statusLedCb ); 
}


static void orientationJobCb(osjob_t *job)
{
    static MMA_orientation last;
           MMA_orientation current;
           int             sleepTime = ORIENTATION_CHECK_PERIOD;

    mma8451q.service();
    current = mma8451q.getOrientation(); 

    if(current != last)
    {
        last = current;
        sleepTime  = ORIENTATION_DEBOUNCE_TIME;
    }
    else 
    {
        const bool currIsVertical = !current.low && current.right;
        const OperMode mode = currIsVertical ? SITE_TEST : RANGE_TEST; 

        if(mode != operMode)
        {
            debug("Change to %s mode\r\n", mode==SITE_TEST?"Site":"Range");

            // onEvent will start test if currently joining or transmitting 
            if(((LMIC.opmode & OP_JOINING) == 0) && ((LMIC.opmode & OP_TXRXPEND) == 0))
            {
                if(operMode != IDLE)
                {
                    os_setCallback( &sendFrameJob, preTestStart);
                }
                else
                {
                    os_setCallback( &sendFrameJob, onSendFrame);
                }
            }

            // Set mode
            operMode = mode; 

            // Reset test indicator LEDs
            ledRed    = 1;
            ledGreen  = 1;
            ledYellow = 1;

            // Reset test state
            txCount = 0;
            siteDr  = 0;
            sitePw  = 0;
            memset(siteAck, 0, sizeof(siteAck));
        }
    }

    os_setTimedCallback( &orientationJob, os_getTime() + sec2osticks(sleepTime), orientationJobCb ); 
}

// Kick off initial jobs
void application_init()
{
    // Start status led job
    os_setCallback(&statusLedJob, statusLedCb); 
    
    // Start orientation job - Test will start once orientation is determined
    os_setCallback(&orientationJob, orientationJobCb); 

    // Get current gps coordinates
    if(gps_service() == true)
        lastTxPos.set(gps.Latitude,gps.Longitude);
    else
        lastTxPos.set(0,0);
}


int main(void)
{
    // initialize runtime env
    os_init();

    // initialize debug system
    debug_init();

    // Display build info
    displayBuildInfo();

    // initialize peripherals
    board_peripherals_init();

    // Application initialization
    application_init();

    // Start protocol
    startLMIC();

    // execute scheduled jobs and events
    os_runloop();
    // (not reached)
}