Senet / Senet NAMote

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

main.cpp

Committer:
shaunkrnelson
Date:
2016-04-22
Revision:
2:b9b2840d0d8b
Parent:
1:fbf2fb2c2718
Child:
3:35e8af53c6cf

File content as of revision 2:b9b2840d0d8b:

#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 0x786532 
#define MAJOR          1
#define MINOR          1
#define POINT          1
#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.left && (current.front||current.back) && !current.low);
        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)
}