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
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:
- No network coverage in that area
- 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
- USR
- Blink fast while joining
- Solid when joined and GPS lock
- Blink slow when not GPS lock
- RED
- 30 dBm status indicator
- Blink on transmit
- Solid after downlink received
- YELLOW
- 20 dBm status indicator
- Blink on transmit
- Solid after ack received
- GREEN
- 14 dBm status indicator
- Blink on transmit
- 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:
- DR3 Tx Power 14
- DR0 Tx Power 14
- DR4 Tx Power 20
- DR3 Tx Power 20
- DR0 Tx Power 20
- DR4 Tx Power 26
- DR3 Tx Power 30
- DR0 Tx Power 30
After transmit cycle completes the next test will start in 560 seconds unless following:
- After 140 seconds if no ack received
- 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
- V3 Mote
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:
- 2016-08-12
- Revision:
- 9:65a83f20cf3b
- Parent:
- 8:6757b3e3bf3d
- Child:
- 10:72c5e2cfa473
File content as of revision 9:65a83f20cf3b:
#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 2 #define BUILD_NUMBER 0 #define DEVELOPER_ID 0 #define DR_TX_FLOATING // 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; // x-y-z orientation uint16_t orientation_mask = 0x0000; // is the device moving or not bool stationary = true; // 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 SENSOR_PACKET: { // Read Sensors mpl3115a2.SetModeActive(); uint16_t pressure = mpl3115a2.ReadBarometer(); uint16_t temperature = mpl3115a2.ReadTemperature(); mpl3115a2.SetModeStandby(); // Sensor packet type serialized to the LMIC frame buffer SensorPacket packet(LMIC.frame, MAX_LEN_FRAME); packet.setPrimarySensor(orientation_mask); packet.setTemperature(temperature); packet.setPressure(pressure); // 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(); #ifdef DR_TX_FLOATING LMIC_setAdrMode(true); #endif // 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); } #ifndef DR_TX_FLOATING // Set next dr and tx power LMIC_setDrTxpow(dr,pw); #else pw = LMIC.txpow; #endif // Send GPS data for(int32_t i=0; i<3; i++) { if(gps_service() == true) { if(!stationary || (0 != txCount&0x7)) pktSize = PrepareDataFrame(GPS_PACKET); break; } } // if unable to get GPS or stationary, send sensor packet if(0 == pktSize) pktSize = PrepareDataFrame(SENSOR_PACKET); 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); stationary = true; 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); stationary = false; } 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); stationary = false; os_setCallback( &sendFrameJob, onSendFrame ); return; } } } else if(os_getTime() >= startT) { os_setCallback( &sendFrameJob, onSendFrame ); } // 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 ); } bool leds_are_down = false; static void orientationJobCb(osjob_t *job) { static MMA_orientation last; MMA_orientation orientation; int sleepTime = ORIENTATION_CHECK_PERIOD; mma8451q.service(); orientation = mma8451q.getOrientation(); if(orientation != last) { last = orientation; sleepTime = ORIENTATION_DEBOUNCE_TIME; } else { /* Get orientation using the portrait/landscape status * When viewing device from front orientation is: * - LED UP = landscape left * - LED LEFT = portrait up * - LED DOWN = landscape right * - LED RIGHT = portrait down * * NOTE: * When low is true the device is flat and the orientation will always be portrait up * */ bool flat = true; bool up = true; bool down = true; bool right = true; bool left = true; if(orientation.low) { flat = true; } else if(orientation.left) { up = true; } else if(orientation.up) { left = true; } else if(orientation.right) { down = true; } else if(orientation.down) { right = true; } const OperMode mode = up ? SITE_TEST : RANGE_TEST; uint16_t new_orientation_mask; if(!flat) { new_orientation_mask = (up << 12) | (left << 8) | (down << 4) | right; } else { new_orientation_mask = (2 << 12) | (orientation.back << 8); } leds_are_down = down; bool send_now = new_orientation_mask != orientation_mask; orientation_mask = new_orientation_mask; 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)); } else if (send_now) os_setCallback( &sendFrameJob, onSendFrame); } 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) }