LMiC LoRa Semtech + Nucleo version sending every 10secs
Dependents: LoRaWAN-test-10secs
Fork of LMiC by
Diff: lmic.cpp
- Revision:
- 1:d3b7bde3995c
- Parent:
- 0:62d1edcc13d1
- Child:
- 2:bebd6b2e3d18
--- a/lmic.cpp Thu Jan 22 12:50:49 2015 +0000 +++ b/lmic.cpp Tue Mar 31 13:36:56 2015 +0000 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014 IBM Corporation. + * Copyright (c) 2014-2015 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -9,10 +9,13 @@ * IBM Zurich Research Lab - initial API, implementation and documentation *******************************************************************************/ +//! \file #include "lmic.h" +#if !defined(MINRX_SYMS) +#define MINRX_SYMS 5 +#endif // !defined(MINRX_SYMS) #define PAMBL_SYMS 8 -#define MINRX_SYMS 5 #define PAMBL_FSK 5 #define PRERX_FSK 1 #define RXLEN_FSK (1+5+2) @@ -29,6 +32,15 @@ #define BCN_GUARD_osticks ms2osticks(BCN_GUARD_ms) #define BCN_WINDOW_osticks ms2osticks(BCN_WINDOW_ms) #define AIRTIME_BCN_osticks us2osticks(AIRTIME_BCN) +#if defined(CFG_eu868) +#define DNW2_SAFETY_ZONE ms2osticks(3000) +#endif +#if defined(CFG_us915) +#define DNW2_SAFETY_ZONE ms2osticks(750) +#endif + +// Special APIs - for development or testing +#define isTESTMODE() 0 DEFINE_LMIC; DECL_ON_LMIC_EVENT; @@ -42,36 +54,36 @@ // ================================================================================ // BEG OS - default implementations for certain OS suport functions -#if !HAS_os_calls +#if !defined(HAS_os_calls) -#ifndef os_rlsbf2 +#if !defined(os_rlsbf2) u2_t os_rlsbf2 (xref2cu1_t buf) { return (u2_t)(buf[0] | (buf[1]<<8)); } #endif -#ifndef os_rlsbf4 +#if !defined(os_rlsbf4) u4_t os_rlsbf4 (xref2cu1_t buf) { return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); } #endif -#ifndef os_rmsbf4 +#if !defined(os_rmsbf4) u4_t os_rmsbf4 (xref2cu1_t buf) { return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); } #endif -#ifndef os_wlsbf2 +#if !defined(os_wlsbf2) void os_wlsbf2 (xref2u1_t buf, u2_t v) { buf[0] = v; buf[1] = v>>8; } #endif -#ifndef os_wlsbf4 +#if !defined(os_wlsbf4) void os_wlsbf4 (xref2u1_t buf, u4_t v) { buf[0] = v; buf[1] = v>>8; @@ -80,7 +92,7 @@ } #endif -#ifndef os_wmsbf4 +#if !defined(os_wmsbf4) void os_wmsbf4 (xref2u1_t buf, u4_t v) { buf[3] = v; buf[2] = v>>8; @@ -89,54 +101,27 @@ } #endif -#ifndef os_getBattLevel +#if !defined(os_getBattLevel) u1_t os_getBattLevel (void) { return MCMD_DEVS_BATT_NOINFO; } #endif -#ifndef os_crc16 -static const u2_t CRC_TABLE[] = { - 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, - 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, - 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, - 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, - 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, - 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, - 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, - 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, - 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, - 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, - 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, - 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, - 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, - 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, - 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, - 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, - 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, - 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, - 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, - 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, - 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, - 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, - 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, - 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, - 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, - 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, - 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, - 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, - 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, - 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, - 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, - 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 -}; +#if !defined(os_crc16) +// New CRC-16 CCITT(XMODEM) checksum for beacons: u2_t os_crc16 (xref2u1_t data, uint len) { - u2_t fcs = 0; - for( uint u=0; u<len; u++ ) { - u1_t b = data[u]; - fcs = (CRC_TABLE[(fcs ^ b) & 0xFF] ^ ((fcs >> 8) & 0xFF)); + u2_t remainder = 0; + u2_t polynomial = 0x1021; + for( uint i = 0; i < len; i++ ) { + remainder ^= data[i] << 8; + for( u1_t bit = 8; bit > 0; bit--) { + if( (remainder & 0x8000) ) + remainder = (remainder << 1) ^ polynomial; + else + remainder <<= 1; + } } - return fcs; + return remainder; } #endif @@ -193,7 +178,7 @@ static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) { if( len <= 0 ) - return; + return; os_clearMem(AESaux, 16); AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1 AESaux[5] = dndir?1:0; @@ -225,7 +210,7 @@ // ================================================================================ // BEG LORA -#if CFG_eu868 // ======================================== +#if defined(CFG_eu868) // ======================================== #define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF) const u1_t maxFrameLens [] = { 64,64,64,123 }; @@ -248,7 +233,7 @@ }; #define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT]) -#elif CFG_us915 // ======================================== +#elif defined(CFG_us915) // ======================================== #define maxFrameLen(dr) ((dr)<=DR_SF11CR ? maxFrameLens[(dr)] : 0xFF) const u1_t maxFrameLens [] = { 24,66,142,255,255,255,255,255, 66,142 }; @@ -296,21 +281,18 @@ u1_t bw = getBw(rps); // 0,1,2 = 125,250,500kHz u1_t sf = getSf(rps); // 0=FSK, 1..6 = SF7..12 if( sf == FSK ) { - // MLu - 12012015 - return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8 - * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000; - //return (plen+/*preamble*/5+/*syncword*/2+/*len*/1+/*crc*/2) * /*bits/byte*/8 - // * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000; + return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8 + * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000; } u1_t sfx = 4*(sf+(7-SF7)); u1_t q = sfx - (sf >= SF11 ? 8 : 0); int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0); if( tmp > 0 ) { - tmp = (tmp + q - 1) / q; - tmp *= getCr(rps)+5; - tmp += 8; + tmp = (tmp + q - 1) / q; + tmp *= getCr(rps)+5; + tmp += 8; } else { - tmp = 8; + tmp = 8; } tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */; // bw = 125000 = 15625 * 2^3 @@ -325,9 +307,9 @@ sfx = sf+(7-SF7) - (3+2) - bw; int div = 15625; if( sfx > 4 ) { - // prevent 32bit signed int overflow in last step - div >>= sfx-4; - sfx = 4; + // prevent 32bit signed int overflow in last step + div >>= sfx-4; + sfx = 4; } // Need 32bit arithmetic for this last step return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; @@ -337,8 +319,6 @@ extern inline int s12rssi (s1_t v); extern inline float s12snr (s1_t v); extern inline s1_t snr2s1 (double v); -extern inline int getRssi (rxqu_t*rxq); -extern inline void setRssi (rxqu_t*rxq, int v); extern inline rps_t updr2rps (dr_t dr); extern inline rps_t dndr2rps (dr_t dr); @@ -374,23 +354,23 @@ // normal frames - 1st try / no retry 0, // confirmed frames - 0,0,0,0,0,1,1,1,2 + 0,0,1,0,1,0,1,0,0 }; // Table below defines the size of one symbol as // symtime = 256us * 2^T(sf,bw) // 256us is called one symunit. -// SF: -// BW: |__7___8___9__10__11__12 -// 125kHz | 2 3 4 5 6 7 -// 250kHz | 1 2 3 4 5 6 -// 500kHz | 0 1 2 3 4 5 +// SF: +// BW: |__7___8___9__10__11__12 +// 125kHz | 2 3 4 5 6 7 +// 250kHz | 1 2 3 4 5 6 +// 500kHz | 0 1 2 3 4 5 // // Times for half symbol per DR // Per DR table to minimize rounding errors static const ostime_t DR2HSYM_osticks[] = { -#if CFG_eu868 +#if defined(CFG_eu868) #define dr2hsym(dr) (DR2HSYM_osticks[(dr)]) us2osticksRound(128<<7), // DR_SF12 us2osticksRound(128<<6), // DR_SF11 @@ -400,7 +380,7 @@ us2osticksRound(128<<2), // DR_SF7 us2osticksRound(128<<1), // DR_SF7B us2osticksRound(80) // FSK -- not used (time for 1/2 byte) -#elif CFG_us915 +#elif defined(CFG_us915) #define dr2hsym(dr) (DR2HSYM_osticks[(dr)&7]) // map DR_SFnCR -> 0-6 us2osticksRound(128<<5), // DR_SF10 DR_SF12CR us2osticksRound(128<<4), // DR_SF9 DR_SF11CR @@ -415,13 +395,13 @@ static ostime_t calcRxWindow (u1_t secs, dr_t dr) { ostime_t rxoff, err; if( secs==0 ) { - // aka 128 secs (next becaon) - rxoff = LMIC.drift; - err = LMIC.lastDriftDiff; + // aka 128 secs (next becaon) + rxoff = LMIC.drift; + err = LMIC.lastDriftDiff; } else { - // scheduled RX window within secs into current beacon period - rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; - err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; + // scheduled RX window within secs into current beacon period + rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; + err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; } u1_t rxsyms = MINRX_SYMS; err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; @@ -434,10 +414,10 @@ // Setup beacon RX parameters assuming we have an error of ms (aka +/-(ms/2)) static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) { if( ini ) { - LMIC.drift = 0; - LMIC.maxDriftDiff = 0; - LMIC.missedBcns = 0; - LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF; + LMIC.drift = 0; + LMIC.maxDriftDiff = 0; + LMIC.missedBcns = 0; + LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF; } ostime_t hsym = dr2hsym(DR_BCN); LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; @@ -455,8 +435,8 @@ u1_t intvExp = rxsched->intvExp; ostime_t off = os_rlsbf2(LMIC.frame) & (0x0FFF >> (7 - intvExp)); // random offset (slot units) rxsched->rxbase = (LMIC.bcninfo.txtime + - BCN_RESERVE_osticks + - ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks + BCN_RESERVE_osticks + + ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks rxsched->slot = 0; rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<<intvExp),rxsched->dr); rxsched->rxsyms = LMIC.rxsyms; @@ -466,16 +446,16 @@ static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) { again: if( rxsched->rxtime - cando >= 0 ) - return 1; + return 1; u1_t slot; if( (slot=rxsched->slot) >= 128 ) - return 0; + return 0; u1_t intv = 1<<rxsched->intvExp; if( (rxsched->slot = (slot += (intv))) >= 128 ) - return 0; + return 0; rxsched->rxtime = rxsched->rxbase - + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp) - - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr); + + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp) + - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr); rxsched->rxsyms = LMIC.rxsyms; goto again; } @@ -485,9 +465,9 @@ u2_t r = os_getRndU2(); ostime_t delay = r; if( delay > OSTICKS_PER_SEC ) - delay = r % (u2_t)OSTICKS_PER_SEC; + delay = r % (u2_t)OSTICKS_PER_SEC; if( secSpan > 0 ) - delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC; + delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC; return delay; } @@ -495,36 +475,39 @@ static void txDelay (ostime_t reftime, u1_t secSpan) { reftime += rndDelay(secSpan); if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) { - LMIC.globalDutyAvail = reftime; - LMIC.opmode |= OP_RNDTX; + LMIC.globalDutyAvail = reftime; + LMIC.opmode |= OP_RNDTX; } } static void setDrJoin (u1_t reason, u1_t dr) { EV(drChange, INFO, (e_.reason = reason, - e_.deveui = MAIN::CDEV->getEui(), - e_.dr = dr|DR_PAGE, - e_.txpow = LMIC.adrTxPow, - e_.prevdr = LMIC.datarate|DR_PAGE, - e_.prevtxpow = LMIC.adrTxPow)); + e_.deveui = MAIN::CDEV->getEui(), + e_.dr = dr|DR_PAGE, + e_.txpow = LMIC.adrTxPow, + e_.prevdr = LMIC.datarate|DR_PAGE, + e_.prevtxpow = LMIC.adrTxPow)); LMIC.datarate = dr; - DO_DEVDB(updateDatarate, dr); + DO_DEVDB(LMIC.datarate,datarate); } static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { EV(drChange, INFO, (e_.reason = reason, - e_.deveui = MAIN::CDEV->getEui(), - e_.dr = dr|DR_PAGE, - e_.txpow = pow, - e_.prevdr = LMIC.datarate|DR_PAGE, - e_.prevtxpow = LMIC.adrTxPow)); + e_.deveui = MAIN::CDEV->getEui(), + e_.dr = dr|DR_PAGE, + e_.txpow = pow, + e_.prevdr = LMIC.datarate|DR_PAGE, + e_.prevtxpow = LMIC.adrTxPow)); + if( pow != KEEP_TXPOW ) - LMIC.adrTxPow = pow; - LMIC.datarate = dr; - DO_DEVDB(updateDatarate, dr); - LMIC.opmode |= OP_NEXTCHNL; + LMIC.adrTxPow = pow; + if( LMIC.datarate != dr ) { + LMIC.datarate = dr; + DO_DEVDB(LMIC.datarate,datarate); + LMIC.opmode |= OP_NEXTCHNL; + } } @@ -540,74 +523,107 @@ // App may call LMIC_enableTracking() explicitely before // Otherwise tracking is implicitly enabled here if( (LMIC.opmode & (OP_TRACK|OP_SCAN)) == 0 && LMIC.bcninfoTries == 0 ) - LMIC_enableTracking(0); + LMIC_enableTracking(0); } -#if CFG_eu868 +#if defined(CFG_eu868) // ================================================================================ // // BEG: EU868 related stuff // -enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2 }; +enum { NUM_DEFAULT_CHANNELS=6 }; static const u4_t iniChannelFreq[12] = { // Join frequencies and duty cycle limit (0.1%) - EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI, - EU868_J4|BAND_MILLI, EU868_J5|BAND_MILLI, EU868_J6|BAND_MILLI, + EU868_F1|BAND_MILLI, EU868_J4|BAND_MILLI, + EU868_F2|BAND_MILLI, EU868_J5|BAND_MILLI, + EU868_F3|BAND_MILLI, EU868_J6|BAND_MILLI, // Default operational frequencies EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI, EU868_F4|BAND_MILLI, EU868_F5|BAND_MILLI, EU868_F6|BAND_DECI }; static void initDefaultChannels (bit_t join) { - LMIC.channelMap = 0x3F; + os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); + os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); + os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); + + LMIC.channelMap = 0x1F; u1_t su = join ? 0 : 6; for( u1_t fu=0; fu<6; fu++,su++ ) { - LMIC.channelFreq[fu] = iniChannelFreq[su]; - LMIC.channelDrs[fu] = DR_SF12 | (DR_SF7<<4); + LMIC.channelFreq[fu] = iniChannelFreq[su]; + LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7); } - if( !join ) - LMIC.channelDrs[1] = DR_SF12 | (DR_FSK<<4); + if( !join ) { + LMIC.channelDrMap[5] = DR_RANGE_MAP(DR_SF12,DR_SF7); + LMIC.channelDrMap[1] = DR_RANGE_MAP(DR_SF12,DR_FSK); + } - LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1% - LMIC.bands[BAND_MILLI].txpow = 14; - LMIC.bands[BAND_CENTI].txcap = 100; // 1% - LMIC.bands[BAND_CENTI].txpow = 14; - LMIC.bands[BAND_DECI ].txcap = 10; // 10% - LMIC.bands[BAND_DECI ].txpow = 27; + LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1% + LMIC.bands[BAND_MILLI].txpow = 14; + LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS; + LMIC.bands[BAND_CENTI].txcap = 100; // 1% + LMIC.bands[BAND_CENTI].txpow = 14; + LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS; + LMIC.bands[BAND_DECI ].txcap = 10; // 10% + LMIC.bands[BAND_DECI ].txpow = 27; + LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS; LMIC.bands[BAND_MILLI].avail = LMIC.bands[BAND_CENTI].avail = LMIC.bands[BAND_DECI ].avail = os_getTime(); } +bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap) { + if( bandidx > BAND_AUX ) return 0; + band_t* b = &LMIC.bands[bandidx]; + b->txpow = txpow; + b->txcap = txcap; + b->avail = os_getTime(); + b->lastchnl = os_getRndU1() % MAX_CHANNELS; + return 1; +} + +bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { + if( chidx >= MAX_CHANNELS ) + return 0; + if( band == -1 ) { + if( freq >= 869400000 && freq <= 869650000 ) + freq |= BAND_DECI; // 10% 27dBm + else if( (freq >= 868000000 && freq <= 868600000) || + (freq >= 869700000 && freq <= 870000000) ) + freq |= BAND_CENTI; // 1% 14dBm + else + freq |= BAND_MILLI; // 0.1% 14dBm + } else { + if( band > BAND_AUX ) return 0; + freq = (freq&~3) | band; + } + LMIC.channelFreq [chidx] = freq; + LMIC.channelDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF12,DR_SF7) : drmap; + LMIC.channelMap |= 1<<chidx; // enabled right away + return 1; +} + +void LMIC_disableChannel (u1_t channel) { + LMIC.channelFreq[channel] = 0; + LMIC.channelDrMap[channel] = 0; + LMIC.channelMap &= ~(1<<channel); +} + static u4_t convFreq (xref2u1_t ptr) { u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100; - if( freq >= EU868_FREQ_MIN && freq <= EU868_FREQ_MAX ) - freq = 0; + if( freq < EU868_FREQ_MIN || freq > EU868_FREQ_MAX ) + freq = 0; return freq; } -static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) { - if( chidx >= MAX_CHANNELS ) - return 0; - if( freq >= 869400000 && freq <= 869650000 ) - freq |= BAND_DECI; // 10% 27dBm - else if( (freq >= 868000000 && freq <= 868600000) || - (freq >= 869700000 && freq <= 870000000) ) - freq |= BAND_CENTI; // 1% 14dBm - //else - // freq |= BAND_MILLI; // 0.1% 14dBm - LMIC.channelFreq[chidx] = freq; - LMIC.channelDrs [chidx] = drs < 0 ? (DR_SF12|(DR_SF7<<4)) : drs; - return 1; -} - static u1_t mapChannels (u1_t chpage, u2_t chmap) { - if( chpage != 0 || (chmap & ~((u2_t)(1<<MAX_CHANNELS)-1)) != 0 ) - return 0; // illegal input + // Bad page, disable all channel, enable non-existent + if( chpage != 0 || chmap==0 || (chmap & ~LMIC.channelMap) != 0 ) + return 0; // illegal input for( u1_t chnl=0; chnl<MAX_CHANNELS; chnl++ ) { - if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 ) - chmap &= ~(1<<chnl); // ignore - channel is not defined + if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 ) + chmap &= ~(1<<chnl); // ignore - channel is not defined } LMIC.channelMap = chmap; return 1; @@ -623,68 +639,39 @@ LMIC.freq = freq & ~(u4_t)3; LMIC.txpow = band->txpow; band->avail = txbeg + airtime * band->txcap; - // Update obsolete avail's to prevent rollover - // (needed for devices that send rarely - e.g. once/twice a day) - for( u1_t b=0; b<MAX_BANDS; b++ ) { - if( LMIC.bands[b].avail - txbeg < 0 ) - LMIC.bands[b].avail = txbeg; - } if( LMIC.globalDutyRate != 0 ) - LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate); + LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate); } static ostime_t nextTx (ostime_t now) { - // If we do not find a suitable channel stop sending (misconfigured device) - ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC; - s1_t bmask = 0; - for( u1_t b=0; b<MAX_BANDS; b++ ) { - xref2band_t band = &LMIC.bands[b]; - if( band->txcap == 0 ) // band not setup - continue; - if( now - band->avail >= 0 ) { - bmask = (bmask < 0 ? 0 : bmask) | (1<<b); - } - else if( mintime - band->avail > 0 ) { - mintime = band->avail; - } - } - if( bmask == 0 ) - return mintime; - u1_t chnl, ccnt; - u2_t cset, mask; - chnl = cset = ccnt = 0; - mask = 1; - while( mask && mask <= LMIC.channelMap ) { - // Channel is enabled AND and in a ready band and can handle the datarate - if( (mask & LMIC.channelMap) != 0 && (bmask & (1 << (LMIC.channelFreq[chnl] & 0x3))) != 0) { - u1_t drs = LMIC.channelDrs[chnl]; - if( !(isSlowerDR((dr_t)LMIC.datarate, (dr_t)(drs & 0xF)) || // lowest datarate - isFasterDR((dr_t)LMIC.datarate, (dr_t)(drs >> 4))) ) { // fastest datarate - cset |= mask; - ccnt++; + u1_t bmap=0xF; + do { + ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC; + u1_t band=0; + for( u1_t bi=0; bi<4; bi++ ) { + if( (bmap & (1<<bi)) && mintime - LMIC.bands[bi].avail > 0 ) + mintime = LMIC.bands[band = bi].avail; } - } - mask <<= 1; - chnl++; - } - if( ccnt == 0 ) // No eligible channel - misconfigured device? - return mintime; - ccnt = os_getRndU2() % ccnt; - mask = 1; - chnl = 0; - do { - if( (cset & mask) != 0 ) { - if( ccnt==0 ) { - LMIC.txChnl = chnl; - return now; + // Find next channel in given band + u1_t chnl = LMIC.bands[band].lastchnl; + for( u1_t ci=0; ci<MAX_CHANNELS; ci++ ) { + if( (chnl = (chnl+1)) >= MAX_CHANNELS ) + chnl -= MAX_CHANNELS; + if( (LMIC.channelMap & (1<<chnl)) != 0 && // channel enabled + (LMIC.channelDrMap[chnl] & (1<<(LMIC.datarate&0xF))) != 0 && + band == (LMIC.channelFreq[chnl] & 0x3) ) { // in selected band + LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; + return mintime; + } } - ccnt--; - } - mask <<= 1; - chnl++; + if( (bmap &= ~(1<<band)) == 0 ) { + // No feasible channel found! + return mintime; + } } while(1); } + static void setBcnRxParams (void) { LMIC.dataLen = 0; LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3; @@ -694,84 +681,96 @@ #define setRx1Params() /*LMIC.freq/rps remain unchanged*/ static void initJoinLoop (void) { - LMIC.txChnl = os_getRndU1() % 3; + LMIC.txChnl = os_getRndU1() % 6; LMIC.adrTxPow = 14; setDrJoin(DRCHG_SET, DR_SF7); initDefaultChannels(1); ASSERT((LMIC.opmode & OP_NEXTCHNL)==0); - LMIC.txend = LMIC.bands[BAND_MILLI].avail; + LMIC.txend = LMIC.bands[BAND_MILLI].avail + rndDelay(8); } static ostime_t nextJoinState (void) { + u1_t failed = 0; + // Try 869.x and then 864.x with same DR // If both fail try next lower datarate + if( ++LMIC.txChnl == 6 ) + LMIC.txChnl = 0; + if( (++LMIC.txCnt & 1) == 0 ) { + // Lower DR every 2nd try (having tried 868.x and 864.x with the same DR) + if( LMIC.datarate == DR_SF12 ) + failed = 1; // we have tried all DR - signal EV_JOIN_FAILED + else + setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate)); + } + // Clear NEXTCHNL because join state engine controls channel hopping LMIC.opmode &= ~OP_NEXTCHNL; - LMIC.txend = LMIC.bands[BAND_MILLI].avail; - if( LMIC.txChnl < 3 ) { - LMIC.txChnl += 3; - } else { - if( LMIC.datarate == DR_SF12 ) { - setDrJoin(DRCHG_NOJACC, DR_SF7); - // We tried one round of FSK..SF12 - // Now pick new random channel and insert a random delay and try another round. - LMIC.txChnl = os_getRndU1() % 3; - if( LMIC.txCnt < 5 ) - LMIC.txCnt += 1; - // Delay by 7,15,31,63,127,255 secs - return rndDelay((4<<LMIC.txCnt)-1) | 1; // |1 - trigger EV_JOIN_FAILED event - } - LMIC.txChnl = LMIC.txChnl==5 ? 0 : LMIC.txChnl-2; - setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate)); - } - // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it) - return 3*OSTICKS_PER_SEC; + // Move txend to randomize synchronized concurrent joins. + // Duty cycle is based on txend. + ostime_t time = os_getTime(); + if( time - LMIC.bands[BAND_MILLI].avail < 0 ) + time = LMIC.bands[BAND_MILLI].avail; + LMIC.txend = time + + (isTESTMODE() + // Avoid collision with JOIN ACCEPT @ SF12 being sent by GW (but we missed it) + ? DNW2_SAFETY_ZONE + // Otherwise: randomize join (street lamp case): + // SF12:255, SF11:127, .., SF7:8secs + : DNW2_SAFETY_ZONE+rndDelay(255>>LMIC.datarate)); + // 1 - triggers EV_JOIN_FAILED event + return failed; } // // END: EU868 related stuff // // ================================================================================ -#elif CFG_us915 +#elif defined(CFG_us915) // ================================================================================ // // BEG: US915 related stuff // + static void initDefaultChannels (void) { for( u1_t i=0; i<4; i++ ) - LMIC.channelMap[i] = 0xFFFF; + LMIC.channelMap[i] = 0xFFFF; LMIC.channelMap[4] = 0x00FF; } static u4_t convFreq (xref2u1_t ptr) { u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100; if( freq >= US915_FREQ_MIN && freq <= US915_FREQ_MAX ) - freq = 0; + freq = 0; return freq; } -static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) { +bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { if( chidx < 72 || chidx >= 72+MAX_XCHANNELS ) - return 0; // channels 0..71 are hardwired + return 0; // channels 0..71 are hardwired chidx -= 72; LMIC.xchFreq[chidx] = freq; - LMIC.xchDrs[chidx] = drs < 0 ? (DR_SF10|(DR_SF8C<<4)) : drs; + LMIC.xchDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF10,DR_SF8C) : drmap; LMIC.channelMap[chidx>>4] |= (1<<(chidx&0xF)); return 1; } +void LMIC_disableChannel (u1_t channel) { + if( channel < 72+MAX_XCHANNELS ) + LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF)); +} static u1_t mapChannels (u1_t chpage, u2_t chmap) { if( chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF ) { - u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000; - for( u1_t u=0; u<4; u++ ) - LMIC.channelMap[u] = en125; - LMIC.channelMap[64/16] = chmap; + u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000; + for( u1_t u=0; u<4; u++ ) + LMIC.channelMap[u] = en125; + LMIC.channelMap[64/16] = chmap; } else { - if( chpage >= (72+MAX_XCHANNELS+15)/16 ) - return 0; - LMIC.channelMap[chpage] = chmap; + if( chpage >= (72+MAX_XCHANNELS+15)/16 ) + return 0; + LMIC.channelMap[chpage] = chmap; } return 1; } @@ -779,77 +778,68 @@ static void updateTx (ostime_t txbeg) { u1_t chnl = LMIC.txChnl; if( chnl < 64 ) { - LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP; - LMIC.txpow = 30; - return; + LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP; + LMIC.txpow = 30; + return; } LMIC.txpow = 26; if( chnl < 64+8 ) { - LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP; + LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP; } else { - ASSERT(chnl < 64+8+MAX_XCHANNELS); - LMIC.freq = LMIC.xchFreq[chnl-72]; + ASSERT(chnl < 64+8+MAX_XCHANNELS); + LMIC.freq = LMIC.xchFreq[chnl-72]; } // Update global duty cycle stats if( LMIC.globalDutyRate != 0 ) { - ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); - LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate); + ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); + LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate); } } // US does not have duty cycling - return now as earliest TX time #define nextTx(now) (_nextTx(),(now)) static void _nextTx (void) { - u1_t chnl = LMIC.txChnl; - u1_t cnt = 0; - bit_t bw500 = (LMIC.datarate >= DR_SF8C); - - if( (LMIC.chRnd & 0xF) == 0 ) { - chnl += LMIC.chRnd>>4; - LMIC.chRnd = os_getRndU1(); + if( LMIC.chRnd==0 ) + LMIC.chRnd = os_getRndU1() & 0x3F; + if( LMIC.datarate >= DR_SF8C ) { // 500kHz + u1_t map = LMIC.channelMap[64/16]&0xFF; + for( u1_t i=0; i<8; i++ ) { + if( (map & (1<<(++LMIC.chRnd & 7))) != 0 ) { + LMIC.txChnl = 64 + (LMIC.chRnd & 7); + return; + } + } + } else { // 125kHz + for( u1_t i=0; i<64; i++ ) { + u1_t chnl = ++LMIC.chRnd & 0x3F; + if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl & 0xF))) != 0 ) { + LMIC.txChnl = chnl; + return; + } + } } - LMIC.chRnd--; - again: - chnl += 1; - if( bw500 ) { - // Only channels 64..71 - chnl = 64+(chnl&0x7); - // At least one 500kHz channel must be enabled - // - otherwise MAC should not have used such a datarate - ASSERT((LMIC.channelMap[64/16]&0xFF)!=0x00); - if( ++cnt == 8 ) - return; // no appropriate channel enabled stay where we are - } else { - // At least one 125kHz channel must be enabled - // - otherwise MAC should not have used DR_SF10-7 - ASSERT((LMIC.channelMap[0]|LMIC.channelMap[1]|LMIC.channelMap[2]|LMIC.channelMap[3])!=0x0000); - chnl = chnl&0x3F; - if( ++cnt == 64 ) - return; // no appropriate channel enabled stay where we are - } - if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl&0xF))) == 0 ) - goto again; // not enabled - LMIC.txChnl = chnl; + // No feasible channel found! Keep old one. } static void setBcnRxParams (void) { LMIC.dataLen = 0; - LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl*US915_500kHz_DNFSTEP; + LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl * US915_500kHz_DNFSTEP; LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN); } -#define setRx1Params(void) { \ +#define setRx1Params() { \ LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ - if( /* TX datarate */LMIC.rxsyms < DR_SF8C ) \ - LMIC.rxsyms += DR_SF10CR - DR_SF10; \ - else if( LMIC.rxsyms == DR_SF8C ) \ - LMIC.rxsyms = DR_SF7CR; \ - LMIC.rps = dndr2rps(LMIC.rxsyms); \ + if( /* TX datarate */LMIC.dndr < DR_SF8C ) \ + LMIC.dndr += DR_SF10CR - DR_SF10; \ + else if( LMIC.dndr == DR_SF8C ) \ + LMIC.dndr = DR_SF7CR; \ + LMIC.rps = dndr2rps(LMIC.dndr); \ } static void initJoinLoop (void) { - LMIC.txChnl = os_getRndU1() & 0x3F; + LMIC.chRnd = 0; + LMIC.txChnl = 0; LMIC.adrTxPow = 20; ASSERT((LMIC.opmode & OP_NEXTCHNL)==0); LMIC.txend = os_getTime(); @@ -861,23 +851,29 @@ // SF7/8/9/10 on a random channel 0..63 // SF8C on a random channel 64..71 // - LMIC.opmode &= ~OP_NEXTCHNL; - LMIC.txend = os_getTime(); + u1_t failed = 0; if( LMIC.datarate != DR_SF8C ) { - LMIC.txChnl = 64+(LMIC.txChnl&7); - LMIC.datarate = DR_SF8C; + LMIC.txChnl = 64+(LMIC.txChnl&7); + setDrJoin(DRCHG_SET, DR_SF8C); } else { - LMIC.txChnl = os_getRndU1() & 0x3F; - LMIC.datarate = DR_SF7 - (LMIC.txCnt++ & 3); - if( (LMIC.txCnt & 3) == 0 ) { - // Tried SF10/SF8C - // Delay by 7,15,31,63,127,255 secs - return rndDelay((4<<(LMIC.txCnt>>2))-1) | 1; // |1 - trigger EV_JOIN_FAILED event + LMIC.txChnl = os_getRndU1() & 0x3F; + s1_t dr = DR_SF7 - ++LMIC.txCnt; + if( dr < DR_SF10 ) { + dr = DR_SF10; + failed = 1; // All DR exhausted - signal failed + } + setDrJoin(DRCHG_SET, dr); } - } - // Always wait 500ms in case there was a TX and we just missed it. - // Sending immediately would not be picked up because GW is still busy txing missed frame. - return (OSTICKS_PER_SEC/2) & ~1; + LMIC.opmode &= ~OP_NEXTCHNL; + LMIC.txend = os_getTime() + + (isTESTMODE() + // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it - GW is still busy) + ? DNW2_SAFETY_ZONE + // Otherwise: randomize join (street lamp case): + // SF10:16, SF9=8,..SF8C:1secs + : rndDelay(16>>LMIC.datarate)); + // 1 - triggers EV_JOIN_FAILED event + return failed; } // @@ -896,13 +892,20 @@ static void reportEvent (ev_t ev) { EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, - e_.eui = MAIN::CDEV->getEui(), - e_.info = ev)); + e_.eui = MAIN::CDEV->getEui(), + e_.info = ev)); ON_LMIC_EVENT(ev); engineUpdate(); } +static void runReset (xref2osjob_t osjob) { + // Disable session + LMIC_reset(); + LMIC_startJoining(); + reportEvent(EV_RESET); +} + static void stateJustJoined (void) { LMIC.seqnoDn = LMIC.seqnoUp = 0; LMIC.rejoinCnt = 0; @@ -927,23 +930,30 @@ static int decodeBeacon (void) { ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this xref2u1_t d = LMIC.frame; - if( os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d, OFF_BCN_CRC1) ) - return 0; // first (common) part fails CRC check + if( +#if CFG_eu868 + d[OFF_BCN_CRC1] != (u1_t)os_crc16(d,OFF_BCN_CRC1) +#elif CFG_us915 + os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d,OFF_BCN_CRC1) +#endif + ) + return 0; // first (common) part fails CRC check // First set of fields is ok u4_t bcnnetid = os_rlsbf4(&d[OFF_BCN_NETID]) & 0xFFFFFF; if( bcnnetid != LMIC.netid ) - return -1; // not the beacon we're looking for + return -1; // not the beacon we're looking for LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL); // Match - update bcninfo structure - os_copyMem((xref2u1_t)&LMIC.bcninfo.rxq, (xref2u1_t)&LMIC.rxq, SIZEOFEXPR(LMIC.rxq)); + LMIC.bcninfo.snr = LMIC.snr; + LMIC.bcninfo.rssi = LMIC.rssi; LMIC.bcninfo.txtime = LMIC.rxtime - AIRTIME_BCN_osticks; LMIC.bcninfo.time = os_rlsbf4(&d[OFF_BCN_TIME]); LMIC.bcninfo.flags |= BCN_PARTIAL; // Check 2nd set if( os_rlsbf2(&d[OFF_BCN_CRC2]) != os_crc16(d,OFF_BCN_CRC2) ) - return 1; + return 1; // Second set of fields is ok LMIC.bcninfo.lat = (s4_t)os_rlsbf4(&d[OFF_BCN_LAT-1]) >> 8; // read as signed 24-bit LMIC.bcninfo.lon = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto @@ -959,16 +969,16 @@ u1_t ftype = hdr & HDR_FTYPE; int dlen = LMIC.dataLen; if( dlen < OFF_DAT_OPTS+4 || - (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || - (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { - // Basic sanity checks failed - EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, - e_.eui = MAIN::CDEV->getEui(), - e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]), - e_.info2 = hdr + (dlen<<8))); + (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || + (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { + // Basic sanity checks failed + EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]), + e_.info2 = hdr + (dlen<<8))); norx: - LMIC.dataLen = 0; - return 0; + LMIC.dataLen = 0; + return 0; } // Validate exact frame length // Note: device address was already read+evaluated in order to arrive here. @@ -981,252 +991,261 @@ int pend = dlen-4; // MIC if( addr != LMIC.devaddr ) { - EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS, - e_.eui = MAIN::CDEV->getEui(), - e_.info = addr, - e_.info2 = LMIC.devaddr)); - goto norx; + EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS, + e_.eui = MAIN::CDEV->getEui(), + e_.info = addr, + e_.info2 = LMIC.devaddr)); + goto norx; } if( poff > pend ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, - e_.eui = MAIN::CDEV->getEui(), - e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16))); - goto norx; + EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16))); + goto norx; } int port = -1; int replayConf = 0; if( pend > poff ) - port = d[poff++]; + port = d[poff++]; seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn); if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_MIC, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = seqno)); - goto norx; - } - if( seqno < LMIC.seqnoDn ) { - if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) { - EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.seqnoDn, - e_.info2 = seqno)); + EV(spe3Cond, ERR, (e_.reason = EV::spe3Cond_t::CORRUPTED_MIC, + e_.eui1 = MAIN::CDEV->getEui(), + e_.info1 = Base::lsbf4(&d[pend]), + e_.info2 = seqno, + e_.info3 = LMIC.devaddr)); goto norx; } - if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) { - EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.seqnoDn, - e_.info2 = seqno)); - goto norx; - } - // Replay of previous sequence number allowed only if - // previous frame and repeated both requested confirmation - replayConf = 1; + if( seqno < LMIC.seqnoDn ) { + if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + goto norx; + } + if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + goto norx; + } + // Replay of previous sequence number allowed only if + // previous frame and repeated both requested confirmation + replayConf = 1; } else { - if( seqno > LMIC.seqnoDn ) { - EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.seqnoDn, - e_.info2 = seqno)); - } - LMIC.seqnoDn = seqno+1; // next number to be expected - DO_DEVDB(updateSeqnoDn, LMIC.seqnoDn); - // DN frame requested confirmation - provide ACK once with next UP frame - LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); + if( seqno > LMIC.seqnoDn ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + } + LMIC.seqnoDn = seqno+1; // next number to be expected + DO_DEVDB(LMIC.seqnoDn,seqnoDn); + // DN frame requested confirmation - provide ACK once with next UP frame + LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); } if( LMIC.dnConf || (fct & FCT_MORE) ) - LMIC.opmode |= OP_POLL; + LMIC.opmode |= OP_POLL; // We heard from network - LMIC.adrChanged = 0; - LMIC.adrAckReq = LINK_CHECK_INIT; + LMIC.adrChanged = LMIC.rejoinCnt = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq = LINK_CHECK_INIT; // Process OPTS - int m = LMIC.rxq.rssi - RSSI_OFF - getSensitivity(LMIC.rps); + int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps); LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m; xref2u1_t opts = &d[OFF_DAT_OPTS]; int oidx = 0; while( oidx < olen ) { - switch( opts[oidx] ) { - case MCMD_LCHK_ANS: { - //int gwmargin = opts[oidx+1]; - //int ngws = opts[oidx+2]; - oidx += 3; - continue; - } - case MCMD_LADR_REQ: { - u1_t p1 = opts[oidx+1]; // txpow + DR - u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channel - u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page - u1_t uprpt = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count - oidx += 5; + switch( opts[oidx] ) { + case MCMD_LCHK_ANS: { + //int gwmargin = opts[oidx+1]; + //int ngws = opts[oidx+2]; + oidx += 3; + continue; + } + case MCMD_LADR_REQ: { + u1_t p1 = opts[oidx+1]; // txpow + DR + u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels + u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page + u1_t uprpt = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count + oidx += 5; - LMIC.ladrAns = 0x80 | // Include an answer into next frame up - MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; - if( !mapChannels(chpage, chmap) ) - LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK; - dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); - if( !validDR(dr) ) { - LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK; - dr = (dr_t)LMIC.datarate; - EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = Base::msbf4(&opts[oidx-4]))); + LMIC.ladrAns = 0x80 | // Include an answer into next frame up + MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; + if( !mapChannels(chpage, chmap) ) + LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK; + dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); + if( !validDR(dr) ) { + LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK; + EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = Base::msbf4(&opts[oidx-4]))); + } + if( (LMIC.ladrAns & 0x7F) == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) { + // Nothing went wrong - use settings + LMIC.upRepeat = uprpt; + setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); + } + LMIC.adrChanged = 1; // Trigger an ACK to NWK + continue; } - LMIC.upRepeat = uprpt; - setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); - LMIC.adrChanged = 1; // Trigger an ACK to NWK - continue; - } - case MCMD_DEVS_REQ: { - LMIC.devsAns = 1; - oidx += 1; - continue; - } - case MCMD_DN2P_SET: { - dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); - u4_t freq = convFreq(&opts[oidx+2]); - oidx += 5; - LMIC.dn2Ans = 0x80; // answer pending - if( validDR(dr) ) - LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK; - if( freq != 0 ) - LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK; - if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) { - LMIC.dn2Dr = dr; - LMIC.dn2Freq = freq; - DO_DEVDB(updateDn2,LMIC.dn2Dr,LMIC.dn2Freq); + case MCMD_DEVS_REQ: { + LMIC.devsAns = 1; + oidx += 1; + continue; + } + case MCMD_DN2P_SET: { + dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); + u4_t freq = convFreq(&opts[oidx+2]); + oidx += 5; + LMIC.dn2Ans = 0x80; // answer pending + if( validDR(dr) ) + LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK; + if( freq != 0 ) + LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK; + if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) { + LMIC.dn2Dr = dr; + LMIC.dn2Freq = freq; + DO_DEVDB(LMIC.dn2Dr,dn2Dr); + DO_DEVDB(LMIC.dn2Freq,dn2Freq); + } + continue; } - continue; - } - case MCMD_DCAP_REQ: { - u1_t cap = opts[oidx+1]; - oidx += 2; - // A value cap=0xFF means device is OFF unless enabled again manually - // We just set duty cap to 0xF which is 0.003% -- pretty much off. - // We don't check 0xF0 bits if cap!=0xFF - LMIC.globalDutyRate = cap & 0xF; - LMIC.globalDutyAvail = os_getTime(); - DO_DEVDB(updateDutyCap,cap); - LMIC.dutyCapAns = 1; - continue; - } - case MCMD_SNCH_REQ: { - u1_t chidx = opts[oidx+1]; // channel - u4_t freq = convFreq(&opts[oidx+2]); // freq - u1_t drs = opts[oidx+5]; // datarate span - LMIC.snchAns = 0x80; - if( freq != 0 && setupChannel(chidx,freq,drs) ) - LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK; - oidx += 6; - } - case MCMD_PING_SET: { - u4_t freq = convFreq(&opts[oidx+1]); - oidx += 4; - u1_t flags = 0x80; - if( freq != 0 ) { - flags |= MCMD_PING_ANS_FQACK; - LMIC.ping.freq = freq; - DO_DEVDB(updateClassB,LMIC.ping.intvExp,freq,LMIC.ping.dr); + case MCMD_DCAP_REQ: { + u1_t cap = opts[oidx+1]; + oidx += 2; + // A value cap=0xFF means device is OFF unless enabled again manually. + if( cap==0xFF ) + LMIC.opmode |= OP_SHUTDOWN; // stop any sending + LMIC.globalDutyRate = cap & 0xF; + LMIC.globalDutyAvail = os_getTime(); + DO_DEVDB(cap,dutyCap); + LMIC.dutyCapAns = 1; + continue; + } + case MCMD_SNCH_REQ: { + u1_t chidx = opts[oidx+1]; // channel + u4_t freq = convFreq(&opts[oidx+2]); // freq + u1_t drs = opts[oidx+5]; // datarate span + LMIC.snchAns = 0x80; + if( freq != 0 && LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(drs&0xF,drs>>4), -1) ) + LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK; + oidx += 6; + continue; } - LMIC.pingSetAns = flags; - continue; - } - case MCMD_BCNI_ANS: { - // Ignore if tracking already enabled - if( (LMIC.opmode & OP_TRACK) == 0 ) { - LMIC.bcnChnl = opts[oidx+3]; - // Enable tracking - bcninfoTries - LMIC.opmode |= OP_TRACK; - // Cleared later in txComplete handling - triggers EV_BEACON_FOUND - ASSERT(LMIC.bcninfoTries!=0); - // Setup RX parameters - LMIC.bcninfo.txtime = (LMIC.rxtime - - calcAirTime(LMIC.rps, LMIC.dataLen) - + ms2osticks(os_rlsbf2(&opts[oidx+1]) * 10) - + ms2osticksCeil(5) - - BCN_INTV_osticks); - LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared) - calcBcnRxWindowFromMillis(10,1); // error of +/-5 ms + case MCMD_PING_SET: { + u4_t freq = convFreq(&opts[oidx+1]); + oidx += 4; + u1_t flags = 0x80; + if( freq != 0 ) { + flags |= MCMD_PING_ANS_FQACK; + LMIC.ping.freq = freq; + DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); + DO_DEVDB(LMIC.ping.freq, pingFreq); + DO_DEVDB(LMIC.ping.dr, pingDr); + } + LMIC.pingSetAns = flags; + continue; + } + case MCMD_BCNI_ANS: { + // Ignore if tracking already enabled + if( (LMIC.opmode & OP_TRACK) == 0 ) { + LMIC.bcnChnl = opts[oidx+3]; + // Enable tracking - bcninfoTries + LMIC.opmode |= OP_TRACK; + // Cleared later in txComplete handling - triggers EV_BEACON_FOUND + ASSERT(LMIC.bcninfoTries!=0); + // Setup RX parameters + LMIC.bcninfo.txtime = (LMIC.rxtime + + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BCNI_TUNIT) + + ms2osticksCeil(MCMD_BCNI_TUNIT/2) + - BCN_INTV_osticks); + LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared) + calcBcnRxWindowFromMillis(MCMD_BCNI_TUNIT,1); // error of +/-N ms - EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BCNI_ANS, - e_.eui = MAIN::CDEV->getEui(), - e_.lostmic = Base::lsbf4(&d[pend]), - e_.info = (LMIC.missedBcns | - (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks - - LMIC.bcnRxtime) << 8)), - e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); + EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BCNI_ANS, + e_.eui = MAIN::CDEV->getEui(), + e_.lostmic = Base::lsbf4(&d[pend]), + e_.info = (LMIC.missedBcns | + (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks + - LMIC.bcnRxtime) << 8)), + e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); + } + oidx += 4; + continue; } - oidx += 4; - continue; - } - } - EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = Base::msbf4(&opts[oidx]))); - break; + } + EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = Base::msbf4(&opts[oidx]))); + break; } if( oidx != olen ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, - e_.eui = MAIN::CDEV->getEui(), - e_.info = 0x1000000 + (oidx) + (olen<<8))); + EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = 0x1000000 + (oidx) + (olen<<8))); } if( !replayConf ) { - // Handle payload only if not a replay - // Decrypt payload - if any - if( port >= 0 && pend-poff > 0 ) - aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff); + // Handle payload only if not a replay + // Decrypt payload - if any + if( port >= 0 && pend-poff > 0 ) + aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff); - EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), - e_.devaddr = LMIC.devaddr, - e_.seqno = seqno, - e_.flags = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN, - e_.mic = Base::lsbf4(&d[pend]), - e_.hdr = d[LORA::OFF_DAT_HDR], - e_.fct = d[LORA::OFF_DAT_FCT], - e_.port = port, - e_.plen = dlen, - e_.olen = olen, - memcpy(e_.opts, opts, olen))); + EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), + e_.devaddr = LMIC.devaddr, + e_.seqno = seqno, + e_.flags = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN, + e_.mic = Base::lsbf4(&d[pend]), + e_.hdr = d[LORA::OFF_DAT_HDR], + e_.fct = d[LORA::OFF_DAT_FCT], + e_.port = port, + e_.plen = dlen, + e_.opts.length = olen, + memcpy(&e_.opts[0], opts, olen))); } else { - EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = seqno)); + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = seqno)); } if( // NWK acks but we don't have a frame pending - (ackup && LMIC.txCnt == 0) || - // We sent up confirmed and we got a response in DNW1/DNW2 - // BUT it did not carry an ACK - this should never happen - // Do not resend and assume frame was not ACKed. - (!ackup && LMIC.txCnt != 0) ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK, - e_.eui = MAIN::CDEV->getEui(), - e_.info = seqno, - e_.info2 = ackup)); + (ackup && LMIC.txCnt == 0) || + // We sent up confirmed and we got a response in DNW1/DNW2 + // BUT it did not carry an ACK - this should never happen + // Do not resend and assume frame was not ACKed. + (!ackup && LMIC.txCnt != 0) ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK, + e_.eui = MAIN::CDEV->getEui(), + e_.info = seqno, + e_.info2 = ackup)); } if( LMIC.txCnt != 0 ) // we requested an ACK - LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; + LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; if( port < 0 ) { - LMIC.txrxFlags |= TXRX_NOPORT; - LMIC.dataBeg = LMIC.dataLen = 0; + LMIC.txrxFlags |= TXRX_NOPORT; + LMIC.dataBeg = poff; + LMIC.dataLen = 0; } else { - LMIC.dataBeg = poff; - LMIC.dataLen = pend-poff; + LMIC.txrxFlags |= TXRX_PORT; + LMIC.dataBeg = poff; + LMIC.dataLen = pend-poff; } return 1; } @@ -1263,27 +1282,25 @@ // Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime static void txDone (ostime_t delay, osjobcb_t func) { - // NOTE: LMIC.rxsyms carries the modified DR for TX (LMIC.datarate is the base DR - [CONFIRM mode]) - u1_t dr = LMIC.rxsyms; // rxschedInit - would overwrite rxsyms if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) { - rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! - LMIC.opmode |= OP_PINGINI; + rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! + LMIC.opmode |= OP_PINGINI; } // Change RX frequency / rps (US only) before we increment txChnl setRx1Params(); // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune // into the middle of the 8 symbols preamble. -#if CFG_eu868 +#if defined(CFG_eu868) if( /* TX datarate */LMIC.rxsyms == DR_FSK ) { - LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); - LMIC.rxsyms = RXLEN_FSK; + LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); + LMIC.rxsyms = RXLEN_FSK; } else #endif { - LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(dr); - LMIC.rxsyms = MINRX_SYMS; + LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dndr); + LMIC.rxsyms = MINRX_SYMS; } os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -1305,86 +1322,90 @@ if( LMIC.dataLen == 0 ) { nojoinframe: - if( (LMIC.opmode & OP_OTA) != 0 ) { - if( (LMIC.opmode & OP_JOINING) == 0 ) { - ASSERT((LMIC.opmode & OP_REJOIN) != 0); - // REJOIN attempt for roaming - LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); - if( LMIC.rejoinCnt < 10 ) - LMIC.rejoinCnt++; - reportEvent(EV_REJOIN_FAILED); + if( (LMIC.opmode & OP_JOINING) == 0 ) { + ASSERT((LMIC.opmode & OP_REJOIN) != 0); + // REJOIN attempt for roaming + LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); + if( LMIC.rejoinCnt < 10 ) + LMIC.rejoinCnt++; + reportEvent(EV_REJOIN_FAILED); + return 1; + } + LMIC.opmode &= ~OP_TXRXPEND; + ostime_t delay = nextJoinState(); + EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.datarate|DR_PAGE, + e_.info2 = osticks2ms(delay))); + // Build next JOIN REQUEST with next engineUpdate call + // Optionally, report join failed. + // Both after a random/chosen amount of ticks. + os_setTimedCallback(&LMIC.osjob, os_getTime()+delay, + (delay&1) != 0 + ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed + : FUNC_ADDR(runEngineUpdate)); // next step to be delayed return 1; } - } - LMIC.opmode &= ~OP_TXRXPEND; - ostime_t delay = nextJoinState(); - EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.datarate|DR_PAGE, - e_.info2 = osticks2ms(delay))); - // Build next JOIN REQUEST with next engineUpdate call - // Optionally, report join failed. - // Both after a random/chosen amount of ticks. - os_setTimedCallback(&LMIC.osjob, os_getTime()+delay, - (delay&1) != 0 - ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed - : FUNC_ADDR(runEngineUpdate)); // next step to be delayed - return 1; - } u1_t hdr = LMIC.frame[0]; u1_t dlen = LMIC.dataLen; u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt! if( (dlen != LEN_JA && dlen != LEN_JAEXT) - || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, - e_.eui = MAIN::CDEV->getEui(), - e_.info = dlen < 4 ? 0 : mic, - e_.info2 = hdr + (dlen<<8))); + || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = dlen < 4 ? 0 : mic, + e_.info2 = hdr + (dlen<<8))); badframe: - goto nojoinframe; + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto nojoinframe; } aes_encrypt(LMIC.frame+1, dlen-1); if( !aes_verifyMic0(LMIC.frame, dlen-4) ) { - EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC, - e_.info = mic)); - goto badframe; + EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC, + e_.info = mic)); + goto badframe; } u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); LMIC.devaddr = addr; - LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_BCN_NETID-1]) >> 8; + LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; -#if CFG_eu868 +#if defined(CFG_eu868) initDefaultChannels(0); #endif if( dlen > LEN_JA ) { - dlen = OFF_CFLIST; -#if CFG_eu868 - u1_t chidx=3; -#elif CFG_us915 - u1_t chidx=72; + dlen = OFF_CFLIST; +#if defined(CFG_eu868) + u1_t chidx=3; +#elif defined(CFG_us915) + u1_t chidx=72; #endif - for( ; chidx<8; chidx++, dlen+=3 ) - setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, -1); + for( ; chidx<8; chidx++, dlen+=3 ) + LMIC_setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, 0, -1); } // already incremented when JOIN REQ got sent off aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], LMIC.nwkKey, LMIC.artKey); - DO_DEVDB(updateJoinAcc, LMIC.devaddr, LMIC.nwkKey, LMIC.artKey); + DO_DEVDB(LMIC.netid, netid); + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.nwkKey, nwkkey); + DO_DEVDB(LMIC.artKey, artkey); EV(joininfo, INFO, (e_.arteui = MAIN::CDEV->getArtEui(), - e_.deveui = MAIN::CDEV->getEui(), - e_.devaddr = LMIC.devaddr, - e_.oldaddr = oldaddr, - e_.nonce = LMIC.devNonce-1, - e_.mic = mic, - e_.flags = ((LMIC.opmode & OP_REJOIN) != 0 - ? EV::joininfo_t::REJOIN_ACCEPT - : EV::joininfo_t::ACCEPT))); + e_.deveui = MAIN::CDEV->getEui(), + e_.devaddr = LMIC.devaddr, + e_.oldaddr = oldaddr, + e_.nonce = LMIC.devNonce-1, + e_.mic = mic, + e_.reason = ((LMIC.opmode & OP_REJOIN) != 0 + ? EV::joininfo_t::REJOIN_ACCEPT + : EV::joininfo_t::ACCEPT))); ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0); if( (LMIC.opmode & OP_REJOIN) != 0 ) { - LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); + // Lower DR every try below current UP DR + LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; stateJustJoined(); @@ -1392,12 +1413,26 @@ return 1; } -static void processRx1Jacc (xref2osjob_t osjob) { + +static void processRx2Jacc (xref2osjob_t osjob) { if( LMIC.dataLen == 0 ) - LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot processJoinAccept(); } + +static void setupRx2Jacc (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); + setupRx2(); +} + + +static void processRx1Jacc (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 || !processJoinAccept() ) + schedRx2(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc)); +} + + static void setupRx1Jacc (xref2osjob_t osjob) { setupRx1(FUNC_ADDR(processRx1Jacc)); } @@ -1412,9 +1447,20 @@ // Fwd decl. static bit_t processDnData(void); +static void processRx2DnDataDelay (xref2osjob_t osjob) { + processDnData(); +} + static void processRx2DnData (xref2osjob_t osjob) { - if( LMIC.dataLen == 0 ) - LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + if( LMIC.dataLen == 0 ) { + LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + // Delay callback processing to avoid up TX while gateway is txing our missed frame! + // Since DNW2 uses SF12 by default we wait 3 secs. + os_setTimedCallback(&LMIC.osjob, + (os_getTime() + DNW2_SAFETY_ZONE + rndDelay(2)), + processRx2DnDataDelay); + return; + } processDnData(); } @@ -1427,7 +1473,7 @@ static void processRx1DnData (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processDnData() ) - schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); + schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); } @@ -1451,77 +1497,80 @@ // Prioritize by importance int end = OFF_DAT_OPTS; if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) { - // Indicate pingability in every UP frame - LMIC.frame[end] = MCMD_PING_IND; - LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4); - end += 2; + // Indicate pingability in every UP frame + LMIC.frame[end] = MCMD_PING_IND; + LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4); + end += 2; } if( LMIC.dutyCapAns ) { - LMIC.frame[end] = MCMD_DCAP_ANS; - end += 1; - LMIC.dutyCapAns = 0; + LMIC.frame[end] = MCMD_DCAP_ANS; + end += 1; + LMIC.dutyCapAns = 0; } if( LMIC.dn2Ans ) { - LMIC.frame[end+0] = MCMD_DN2P_ANS; - LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; - end += 2; - LMIC.dn2Ans = 0; + LMIC.frame[end+0] = MCMD_DN2P_ANS; + LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; + end += 2; + LMIC.dn2Ans = 0; } if( LMIC.devsAns ) { // answer to device status - LMIC.frame[end+0] = MCMD_DEVS_ANS; - LMIC.frame[end+1] = LMIC.margin; - LMIC.frame[end+2] = os_getBattLevel(); - end += 3; - LMIC.devsAns = 0; + LMIC.frame[end+0] = MCMD_DEVS_ANS; + LMIC.frame[end+1] = LMIC.margin; + LMIC.frame[end+2] = os_getBattLevel(); + end += 3; + LMIC.devsAns = 0; } if( LMIC.ladrAns ) { // answer to ADR change - LMIC.frame[end+0] = MCMD_LADR_ANS; - LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU; - end += 2; - LMIC.ladrAns = 0; + LMIC.frame[end+0] = MCMD_LADR_ANS; + LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU; + end += 2; + LMIC.ladrAns = 0; } if( LMIC.bcninfoTries > 0 ) { - LMIC.frame[end] = MCMD_BCNI_REQ; - end += 1; + LMIC.frame[end] = MCMD_BCNI_REQ; + end += 1; } if( LMIC.adrChanged ) { - LMIC.adrAckReq = LMIC.adrAckReq < 0 ? 0 : LMIC.adrAckReq; - LMIC.adrChanged = 0; + if( LMIC.adrAckReq < 0 ) + LMIC.adrAckReq = 0; + LMIC.adrChanged = 0; } if( LMIC.pingSetAns != 0 ) { - LMIC.frame[end+0] = MCMD_PING_ANS; - LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU; - end += 2; - LMIC.pingSetAns = 0; + LMIC.frame[end+0] = MCMD_PING_ANS; + LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU; + end += 2; + LMIC.pingSetAns = 0; } if( LMIC.snchAns ) { - LMIC.frame[end+0] = MCMD_SNCH_ANS; - LMIC.frame[end+1] = LMIC.snchAns; - end += 2; - LMIC.snchAns = 0; + LMIC.frame[end+0] = MCMD_SNCH_ANS; + LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU; + end += 2; + LMIC.snchAns = 0; } ASSERT(end <= OFF_DAT_OPTS+16); u1_t flen = end + (txdata ? 5+dlen : 4); if( flen > MAX_LEN_FRAME ) { - // Options and payload too big - delay payload - txdata = 0; - flen = end+4; + // Options and payload too big - delay payload + txdata = 0; + flen = end+4; } LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled - | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0) - | (end-OFF_DAT_OPTS)); + | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0) + | (end-OFF_DAT_OPTS)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); if( LMIC.txCnt == 0 ) { - LMIC.seqnoUp += 1; - DO_DEVDB(updateSeqnoUp, LMIC.seqnoUp); + LMIC.seqnoUp += 1; + DO_DEVDB(LMIC.seqnoUp,seqnoUp); } else { - EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.seqnoUp-1, - e_.info2 = (LMIC.txCnt+1) | (DRADJUST[LMIC.txCnt+1] << 8) | ((LMIC.datarate|DR_PAGE)<<16))); + EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoUp-1, + e_.info2 = ((LMIC.txCnt+1) | + (DRADJUST[LMIC.txCnt+1] << 8) | + ((LMIC.datarate|DR_PAGE)<<16)))); } os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1); @@ -1529,30 +1578,30 @@ LMIC.dnConf = 0; if( txdata ) { - if( LMIC.pendTxConf ) { - // Confirmed only makes sense if we have a payload (or at least a port) - LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1; - LMIC.txCnt += 1; - } - LMIC.frame[end] = LMIC.pendTxPort; - os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen); - aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey, - LMIC.devaddr, LMIC.seqnoUp-1, - /*up*/0, LMIC.frame+end+1, dlen); + if( LMIC.pendTxConf ) { + // Confirmed only makes sense if we have a payload (or at least a port) + LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1; + if( LMIC.txCnt == 0 ) LMIC.txCnt = 1; + } + LMIC.frame[end] = LMIC.pendTxPort; + os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen); + aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey, + LMIC.devaddr, LMIC.seqnoUp-1, + /*up*/0, LMIC.frame+end+1, dlen); } aes_appendMic(LMIC.nwkKey, LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame, flen-4); EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), - e_.devaddr = LMIC.devaddr, - e_.seqno = LMIC.seqnoUp-1, - e_.flags = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP), - e_.mic = Base::lsbf4(&LMIC.frame[flen-4]), - e_.hdr = LMIC.frame[LORA::OFF_DAT_HDR], - e_.fct = LMIC.frame[LORA::OFF_DAT_FCT], - e_.port = LMIC.pendTxPort, - e_.plen = txdata ? dlen : 0, - e_.olen = end-LORA::OFF_DAT_OPTS, - memcpy(e_.opts, LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS))); + e_.devaddr = LMIC.devaddr, + e_.seqno = LMIC.seqnoUp-1, + e_.flags = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP), + e_.mic = Base::lsbf4(&LMIC.frame[flen-4]), + e_.hdr = LMIC.frame[LORA::OFF_DAT_HDR], + e_.fct = LMIC.frame[LORA::OFF_DAT_FCT], + e_.port = LMIC.pendTxPort, + e_.plen = txdata ? dlen : 0, + e_.opts.length = end-LORA::OFF_DAT_OPTS, + memcpy(&e_.opts[0], LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS))); LMIC.dataLen = flen; } @@ -1563,17 +1612,17 @@ os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); if( LMIC.dataLen == 0 ) { - // Nothing received - timeout - LMIC.opmode &= ~(OP_SCAN | OP_TRACK); - reportEvent(EV_SCAN_TIMEOUT); - return; + // Nothing received - timeout + LMIC.opmode &= ~(OP_SCAN | OP_TRACK); + reportEvent(EV_SCAN_TIMEOUT); + return; } if( decodeBeacon() <= 0 ) { - // Something is wrong with the beacon - continue scan - LMIC.dataLen = 0; - os_radio(RADIO_RXON); - os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx)); - return; + // Something is wrong with the beacon - continue scan + LMIC.dataLen = 0; + os_radio(RADIO_RXON); + os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx)); + return; } // Found our 1st beacon // We don't have a previous beacon to calc some drift - assume @@ -1593,7 +1642,7 @@ static void startScan (void) { ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0); if( (LMIC.opmode & OP_SHUTDOWN) != 0 ) - return; + return; // Cancel onging TX/RX transaction LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND); @@ -1606,11 +1655,11 @@ bit_t LMIC_enableTracking (u1_t tryBcnInfo) { if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 ) - return 0; // already in progress or failed to enable + return 0; // already in progress or failed to enable // If BCN info requested from NWK then app has to take are // of sending data up so that MCMD_BCNI_REQ can be attached. if( (LMIC.bcninfoTries = tryBcnInfo) == 0 ) - startScan(); + startScan(); return 1; // enabled } @@ -1639,16 +1688,16 @@ aes_appendMic0(d, OFF_JR_MIC); EV(joininfo,INFO,(e_.deveui = MAIN::CDEV->getEui(), - e_.arteui = MAIN::CDEV->getArtEui(), - e_.nonce = LMIC.devNonce, - e_.oldaddr = LMIC.devaddr, - e_.mic = Base::lsbf4(&d[LORA::OFF_JR_MIC]), - e_.flags = ((LMIC.opmode & OP_REJOIN) != 0 - ? EV::joininfo_t::REJOIN_REQUEST - : EV::joininfo_t::REQUEST))); + e_.arteui = MAIN::CDEV->getArtEui(), + e_.nonce = LMIC.devNonce, + e_.oldaddr = LMIC.devaddr, + e_.mic = Base::lsbf4(&d[LORA::OFF_JR_MIC]), + e_.reason = ((LMIC.opmode & OP_REJOIN) != 0 + ? EV::joininfo_t::REJOIN_REQUEST + : EV::joininfo_t::REQUEST))); LMIC.dataLen = LEN_JR; LMIC.devNonce++; - DO_DEVDB(updateDevNonce, LMIC.devNonce); + DO_DEVDB(LMIC.devNonce,devNonce); } static void startJoining (xref2osjob_t osjob) { @@ -1658,19 +1707,21 @@ // Start join procedure if not already joined. bit_t LMIC_startJoining (void) { if( LMIC.devaddr == 0 ) { - // There should be no TX/RX going on - ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0); - // Cancel scanning - LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); - // Setup state - LMIC.rejoinCnt = LMIC.txCnt = 0; - initJoinLoop(); - LMIC.opmode |= OP_OTA|OP_JOINING; - // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS - os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); - return 1; + // There should be no TX/RX going on + ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0); + // Lift any previous duty limitation + LMIC.globalDutyRate = 0; + // Cancel scanning + LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); + // Setup state + LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0; + initJoinLoop(); + LMIC.opmode |= OP_JOINING; + // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS + os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); + return 1; } - return 0; + return 0; // already joined } @@ -1682,11 +1733,11 @@ static void processPingRx (xref2osjob_t osjob) { if( LMIC.dataLen != 0 ) { - LMIC.txrxFlags = TXRX_PING; - if( decodeFrame() ) { - reportEvent(EV_RXCOMPLETE); - return; - } + LMIC.txrxFlags = TXRX_PING; + if( decodeFrame() ) { + reportEvent(EV_RXCOMPLETE); + return; + } } // Pick next ping slot engineUpdate(); @@ -1698,66 +1749,61 @@ if( LMIC.dataLen == 0 ) { norx: - if( LMIC.txCnt != 0 ) { - if( LMIC.txCnt <= TXCONF_ATTEMPTS ) { - // Schedule another retransmission - txDelay(LMIC.rxtime, RETRY_PERIOD_secs); - LMIC.opmode &= ~OP_TXRXPEND; - engineUpdate(); - return 1; + if( LMIC.txCnt != 0 ) { + if( LMIC.txCnt < TXCONF_ATTEMPTS ) { + LMIC.txCnt += 1; + setDrTxpow(DRCHG_NOACK, lowerDR(LMIC.datarate, DRADJUST[LMIC.txCnt]), KEEP_TXPOW); + // Schedule another retransmission + txDelay(LMIC.rxtime, RETRY_PERIOD_secs); + LMIC.opmode &= ~OP_TXRXPEND; + engineUpdate(); + return 1; + } + LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT; + } else { + // Nothing received - implies no port + LMIC.txrxFlags = TXRX_NOPORT; } - LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT; - } else { - // Nothing received - implies no port - LMIC.txrxFlags = TXRX_NOPORT; - } - LMIC.adrAckReq += 1; - LMIC.dataBeg = LMIC.dataLen = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq += 1; + LMIC.dataBeg = LMIC.dataLen = 0; txcomplete: - LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); - if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { - LMIC.opmode &= ~OP_LINKDEAD; - reportEvent(EV_LINK_ALIVE); - } - reportEvent(EV_TXCOMPLETE); - // If we haven't heard from NWK in a while although we asked for a sign - // assume link is dead - notify application and keep going - if( LMIC.adrAckReq > LINK_CHECK_DEAD ) { - // We haven't heard from NWK for some time although - // We asked for a response for some time - assume we're disconnected. Lower DR one notch. - EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.adrAckReq)); - setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW); - LMIC.adrAckReq = LINK_CHECK_CONT; - if( (LMIC.opmode & OP_OTA) != 0 ) { + LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); + if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { + LMIC.opmode &= ~OP_LINKDEAD; + reportEvent(EV_LINK_ALIVE); + } + reportEvent(EV_TXCOMPLETE); + // If we haven't heard from NWK in a while although we asked for a sign + // assume link is dead - notify application and keep going + if( LMIC.adrAckReq > LINK_CHECK_DEAD ) { + // We haven't heard from NWK for some time although we + // asked for a response for some time - assume we're disconnected. Lower DR one notch. + EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.adrAckReq)); + setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW); + LMIC.adrAckReq = LINK_CHECK_CONT; LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; - } - else { - LMIC.opmode |= OP_LINKDEAD; + reportEvent(EV_LINK_DEAD); } - reportEvent(EV_LINK_DEAD); - } - // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan - if( LMIC.bcninfoTries > 0 ) { - if( (LMIC.opmode & OP_TRACK) != 0 ) { - reportEvent(EV_BEACON_FOUND); - LMIC.bcninfoTries = 0; + // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan + if( LMIC.bcninfoTries > 0 ) { + if( (LMIC.opmode & OP_TRACK) != 0 ) { + reportEvent(EV_BEACON_FOUND); + LMIC.bcninfoTries = 0; + } + else if( --LMIC.bcninfoTries == 0 ) { + startScan(); // NWK did not answer - try scan + } } - else if( --LMIC.bcninfoTries == 0 ) { - startScan(); // NWK did not answer - try scan - } - } - return 1; + return 1; } if( !decodeFrame() ) { - if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) - return 0; - goto norx; + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto norx; } - // If we received a frame reset counter - LMIC.adrAckReq = LINK_CHECK_INIT; - LMIC.rejoinCnt = 0; goto txcomplete; } @@ -1768,55 +1814,57 @@ ev_t ev; if( LMIC.dataLen != 0 && decodeBeacon() >= 1 ) { - ev = EV_BEACON_TRACKED; - if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) { - // We don't have a previous beacon to calc some drift - assume - // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm - calcBcnRxWindowFromMillis(13,0); - goto rev; - } - // We have a previous BEACON to calculate some drift - s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx); - if( LMIC.missedBcns > 0 ) { - drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1); - } - if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) { - s2_t diff = LMIC.drift - drift; - if( diff < 0 ) diff = -diff; - LMIC.lastDriftDiff = diff; - if( LMIC.maxDriftDiff < diff ) - LMIC.maxDriftDiff = diff; - LMIC.bcninfo.flags &= ~BCN_NODDIFF; - } - LMIC.drift = drift; - LMIC.missedBcns = LMIC.rejoinCnt = 0; - LMIC.bcninfo.flags &= ~BCN_NODRIFT; - EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT, - e_.info = drift, - e_.info2 = /*occasion BEACON*/0)); - ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0); + ev = EV_BEACON_TRACKED; + if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) { + // We don't have a previous beacon to calc some drift - assume + // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm + calcBcnRxWindowFromMillis(13,0); + goto rev; + } + // We have a previous BEACON to calculate some drift + s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx); + if( LMIC.missedBcns > 0 ) { + drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1); + } + if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) { + s2_t diff = LMIC.drift - drift; + if( diff < 0 ) diff = -diff; + LMIC.lastDriftDiff = diff; + if( LMIC.maxDriftDiff < diff ) + LMIC.maxDriftDiff = diff; + LMIC.bcninfo.flags &= ~BCN_NODDIFF; + } + LMIC.drift = drift; + LMIC.missedBcns = LMIC.rejoinCnt = 0; + LMIC.bcninfo.flags &= ~BCN_NODRIFT; + EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT, + e_.eui = MAIN::CDEV->getEui(), + e_.info = drift, + e_.info2 = /*occasion BEACON*/0)); + ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0); } else { - ev = EV_BEACON_MISSED; - LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift; - LMIC.bcninfo.time += BCN_INTV_sec; - LMIC.missedBcns++; - // Delay any possible TX after surmised beacon - it's there although we missed it - txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4); - if( (LMIC.opmode & OP_OTA) != 0 ) { - if( LMIC.missedBcns > MAX_MISSED_BCNS ) - LMIC.opmode |= OP_REJOIN; // try if we can roam to another network - } - if( LMIC.bcnRxsyms > MAX_RXSYMS ) { - LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); - reportEvent(EV_LOST_TSYNC); - return; - } + ev = EV_BEACON_MISSED; + LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift; + LMIC.bcninfo.time += BCN_INTV_sec; + LMIC.missedBcns++; + // Delay any possible TX after surmised beacon - it's there although we missed it + txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4); + if( LMIC.missedBcns > MAX_MISSED_BCNS ) + LMIC.opmode |= OP_REJOIN; // try if we can roam to another network + if( LMIC.bcnRxsyms > MAX_RXSYMS ) { + LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); + reportEvent(EV_LOST_TSYNC); + return; + } } LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN); LMIC.bcnRxsyms = LMIC.rxsyms; rev: +#if CFG_us915 + LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; +#endif if( (LMIC.opmode & OP_PINGINI) != 0 ) - rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! + rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! reportEvent(ev); } @@ -1837,11 +1885,11 @@ static void engineUpdate (void) { // Check for ongoing state: scan or TX/RX transaction if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) - return; + return; if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { - LMIC_startJoining(); - return; + LMIC_startJoining(); + return; } ostime_t now = os_getTime(); @@ -1849,123 +1897,131 @@ ostime_t txbeg = 0; if( (LMIC.opmode & OP_TRACK) != 0 ) { - // We are tracking a beacon - ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); - rxtime = LMIC.bcnRxtime - RX_RAMPUP; + // We are tracking a beacon + ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); + rxtime = LMIC.bcnRxtime - RX_RAMPUP; } if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { - // Need to TX some data... - // Assuming txChnl points to channel which first becomes available again. - bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0); - // Find next suitable channel and return availability time - if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) { - txbeg = LMIC.txend = nextTx(now); - LMIC.opmode &= ~OP_NEXTCHNL; - } else { - txbeg = LMIC.txend; - } - // Delayed TX or waiting for duty cycle? - if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) - txbeg = LMIC.globalDutyAvail; - // If we're tracking a beacon... - // then make sure TX-RX transaction is complete before beacon - if( (LMIC.opmode & OP_TRACK) != 0 && - txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) { - // Not enough time to complete TX-RX before beacon - postpone after beacon. - // In order to avoid clustering of postponed TX right after beacon randomize start! - txDelay(rxtime + BCN_RESERVE_osticks, 16); - txbeg = 0; - goto checkrx; - } - // Earliest possible time vs overhead to setup radio - if( txbeg - (now + TX_RAMPUP) < 0 ) { - // We could send right now! - dr_t txdr = (dr_t)LMIC.datarate; - if( jacc ) { - u1_t ftype; - if( (LMIC.opmode & OP_REJOIN) != 0 ) { - txdr = lowerDR(txdr, LMIC.rejoinCnt); - ftype = HDR_FTYPE_REJOIN; + // Need to TX some data... + // Assuming txChnl points to channel which first becomes available again. + bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0); + // Find next suitable channel and return availability time + if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) { + txbeg = LMIC.txend = nextTx(now); + LMIC.opmode &= ~OP_NEXTCHNL; } else { - ftype = HDR_FTYPE_JREQ; + txbeg = LMIC.txend; + } + // Delayed TX or waiting for duty cycle? + if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) + txbeg = LMIC.globalDutyAvail; + // If we're tracking a beacon... + // then make sure TX-RX transaction is complete before beacon + if( (LMIC.opmode & OP_TRACK) != 0 && + txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) { + // Not enough time to complete TX-RX before beacon - postpone after beacon. + // In order to avoid clustering of postponed TX right after beacon randomize start! + txDelay(rxtime + BCN_RESERVE_osticks, 16); + txbeg = 0; + goto checkrx; } - buildJoinRequest(ftype); - LMIC.osjob.func = FUNC_ADDR(jreqDone); - } else { - if( LMIC.seqnoUp == 0xFFFFFFFF ) { - // Roll over of up seq counter - EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER, - e_.eui = MAIN::CDEV->getEui())); - // Rerun join procedure - start with current datarate - LMIC.devaddr = 0; - LMIC_startJoining(); - reportEvent(EV_RESET); + // Earliest possible time vs overhead to setup radio + if( txbeg - (now + TX_RAMPUP) < 0 ) { + // We could send right now! + dr_t txdr = (dr_t)LMIC.datarate; + if( jacc ) { + u1_t ftype; + if( (LMIC.opmode & OP_REJOIN) != 0 ) { + txdr = lowerDR(txdr, LMIC.rejoinCnt); + ftype = HDR_FTYPE_REJOIN; + } else { + ftype = HDR_FTYPE_JREQ; + } + buildJoinRequest(ftype); + LMIC.osjob.func = FUNC_ADDR(jreqDone); + } else { + if( LMIC.seqnoDn >= 0xFFFFFF80 ) { + // Imminent roll over - proactively reset MAC + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = 0)); + // Device has to react! NWK will not roll over and just stop sending. + // Thus, we have N frames to detect a possible lock up. + reset: + os_setCallback(&LMIC.osjob, FUNC_ADDR(runReset)); + return; + } + if( (LMIC.txCnt==0 && LMIC.seqnoUp == 0xFFFFFFFF) ) { + // Roll over of up seq counter + EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info2 = LMIC.seqnoUp)); + // Do not run RESET event callback from here! + // App code might do some stuff after send unaware of RESET. + goto reset; + } + buildDataFrame(); + LMIC.osjob.func = FUNC_ADDR(updataDone); + } + LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr); + LMIC.dndr = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1 + LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; + updateTx(txbeg); + os_radio(RADIO_TX); return; } - buildDataFrame(); - // NOTE: a channel decision for LMIC.txChnl above will never be rendered invalid - // by chosing a slower datarate (it could be invalided ONLY by a faster datarate). - txdr = lowerDR(txdr, DRADJUST[LMIC.txCnt]); - LMIC.osjob.func = FUNC_ADDR(updataDone); - } - LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr); - LMIC.rxsyms = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1 - LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; - updateTx(txbeg); - os_radio(RADIO_TX); - return; - } - // Cannot yet TX - if( (LMIC.opmode & OP_TRACK) == 0 ) - goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX - // Consider RX tasks - if( txbeg == 0 ) // zero indicates no TX pending - txbeg += 1; // TX delayed by one tick (insignificant amount of time) + // Cannot yet TX + if( (LMIC.opmode & OP_TRACK) == 0 ) + goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX + // Consider RX tasks + if( txbeg == 0 ) // zero indicates no TX pending + txbeg += 1; // TX delayed by one tick (insignificant amount of time) } else { - // No TX pending - no scheduled RX - if( (LMIC.opmode & OP_TRACK) == 0 ) - return; + // No TX pending - no scheduled RX + if( (LMIC.opmode & OP_TRACK) == 0 ) + return; } // Are we pingable? checkrx: if( (LMIC.opmode & OP_PINGINI) != 0 ) { - // One more RX slot in this beacon period? - if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) { - if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 ) - goto txdelay; - LMIC.rxsyms = LMIC.ping.rxsyms; - LMIC.rxtime = LMIC.ping.rxtime; - LMIC.freq = LMIC.ping.freq; - LMIC.rps = dndr2rps(LMIC.ping.dr); - LMIC.dataLen = 0; - ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 ); - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing)); - return; - } - // no - just wait for the beacon + // One more RX slot in this beacon period? + if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) { + if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 ) + goto txdelay; + LMIC.rxsyms = LMIC.ping.rxsyms; + LMIC.rxtime = LMIC.ping.rxtime; + LMIC.freq = LMIC.ping.freq; + LMIC.rps = dndr2rps(LMIC.ping.dr); + LMIC.dataLen = 0; + ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 ); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing)); + return; + } + // no - just wait for the beacon } if( txbeg != 0 && (txbeg - rxtime) < 0 ) - goto txdelay; + goto txdelay; setBcnRxParams(); LMIC.rxsyms = LMIC.bcnRxsyms; LMIC.rxtime = LMIC.bcnRxtime; if( now - rxtime >= 0 ) { - LMIC.osjob.func = FUNC_ADDR(processBeacon); - os_radio(RADIO_RX); - return; + LMIC.osjob.func = FUNC_ADDR(processBeacon); + os_radio(RADIO_RX); + return; } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); return; txdelay: EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY, - e_.eui = MAIN::CDEV->getEui(), - e_.info = osticks2ms(txbeg-now), - e_.info2 = LMIC.seqnoUp-1)); + e_.eui = MAIN::CDEV->getEui(), + e_.info = osticks2ms(txbeg-now), + e_.info2 = LMIC.seqnoUp-1)); os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); } @@ -1990,22 +2046,32 @@ void LMIC_reset (void) { EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, - e_.eui = MAIN::CDEV->getEui(), - e_.info = EV_RESET)); + e_.eui = MAIN::CDEV->getEui(), + e_.info = EV_RESET)); os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC)); - LMIC.devaddr = 0; - LMIC.devNonce = os_getRndU2(); - LMIC.opmode = OP_NONE; - LMIC.errcr = CR_4_5; - LMIC.adrEnabled = FCT_ADREN; - LMIC.dn2Dr = DR_DNW2; // we need this for 2ns DN window of join accept - LMIC.dn2Freq = FREQ_DNW2; // ditto -#if CFG_us915 + LMIC.devaddr = 0; + LMIC.devNonce = os_getRndU2(); + LMIC.opmode = OP_NONE; + LMIC.errcr = CR_4_5; + LMIC.adrEnabled = FCT_ADREN; + LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept + LMIC.dn2Freq = FREQ_DNW2; // ditto + LMIC.ping.freq = FREQ_PING; // defaults for ping + LMIC.ping.dr = DR_PING; // ditto + LMIC.ping.intvExp = 0xFF; +#if defined(CFG_us915) initDefaultChannels(); #endif + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.devNonce, devNonce); + DO_DEVDB(LMIC.dn2Dr, dn2Dr); + DO_DEVDB(LMIC.dn2Freq, dn2Freq); + DO_DEVDB(LMIC.ping.freq, pingFreq); + DO_DEVDB(LMIC.ping.dr, pingDr); + DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); } @@ -2018,7 +2084,7 @@ LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL); LMIC.pendTxLen = 0; if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING - return; + return; os_clearCallback(&LMIC.osjob); os_radio(RADIO_RST); engineUpdate(); @@ -2028,7 +2094,7 @@ void LMIC_setTxData (void) { LMIC.opmode |= OP_TXDATA; if( (LMIC.opmode & OP_JOINING) == 0 ) - LMIC.txCnt = 0; // cancel any ongoing TX/RX retries + LMIC.txCnt = 0; // cancel any ongoing TX/RX retries engineUpdate(); } @@ -2036,9 +2102,9 @@ // int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) - return -2; + return -2; if( data != (xref2u1_t)0 ) - os_copyMem(LMIC.pendTxData, data, dlen); + os_copyMem(LMIC.pendTxData, data, dlen); LMIC.pendTxConf = confirmed; LMIC.pendTxPort = port; LMIC.pendTxLen = dlen; @@ -2060,19 +2126,48 @@ engineUpdate(); } -void LMIC_startABP(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey) -{ +//! \brief Setup given session keys +//! and put the MAC in a state as if +//! a join request/accept would have negotiated just these keys. +//! It is crucial that the combinations `devaddr/nwkkey` and `devaddr/artkey` +//! are unique within the network identified by `netid`. +//! NOTE: on Harvard architectures when session keys are in flash: +//! Caller has to fill in LMIC.{nwk,art}Key before and pass {nwk,art}Key are NULL +//! \param netid a 24 bit number describing the network id this device is using +//! \param devaddr the 32 bit session address of the device. It is strongly recommended +//! to ensure that different devices use different numbers with high probability. +//! \param nwkKey the 16 byte network session key used for message integrity. +//! If NULL the caller has copied the key into `LMIC.nwkKey` before. +//! \param artKey the 16 byte application router session key used for message confidentiality. +//! If NULL the caller has copied the key into `LMIC.artKey` before. +void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey) { LMIC.netid = netid; LMIC.devaddr = devaddr; - memcpy(LMIC.nwkKey, nwkKey, 16); - memcpy(LMIC.artKey, artKey, 16); + if( nwkKey != (xref2u1_t)0 ) + os_copyMem(LMIC.nwkKey, nwkKey, 16); + if( artKey != (xref2u1_t)0 ) + os_copyMem(LMIC.artKey, artKey, 16); -#if CFG_eu868 +#if defined(CFG_eu868) initDefaultChannels(0); #endif - - LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; + + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); + LMIC.opmode |= OP_NEXTCHNL; stateJustJoined(); - //engineUpdate(); - reportEvent(EV_JOINED); } + +// Enable/disable link check validation. +// LMIC sets the ADRACKREQ bit in UP frames if there were no DN frames +// for a while. It expects the network to provide a DN message to prove +// connectivity with a span of UP frames. If this no such prove is coming +// then the datarate is lowered and a LINK_DEAD event is generated. +// This mode can be disabled and no connectivity prove (ADRACKREQ) is requested +// nor is the datarate changed. +// This must be called only if a session is established (e.g. after EV_JOINED) +void LMIC_setLinkCheckMode (bit_t enabled) { + LMIC.adrChanged = 0; + LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF; +} + +