/* * Copyright (c) 2014-2016 IBM Corporation. * All rights reserved. * * Copyright (c) 2016-2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ //! \file #define LMIC_DR_LEGACY 0 #include "lmic_bandplan.h" #if defined(DISABLE_BEACONS) && !defined(DISABLE_PING) #error Ping needs beacon tracking #endif DEFINE_LMIC; // Fwd decls. static void reportEventNoUpdate(ev_t); static void reportEventAndUpdate(ev_t); static void engineUpdate(void); static bit_t processJoinAccept_badframe(void); static bit_t processJoinAccept_nojoinframe(void); #if !defined(DISABLE_BEACONS) static void startScan (void); #endif // set the txrxFlags, with debugging static inline void initTxrxFlags(const char *func, u1_t mask) { LMIC_DEBUG2_PARAMETER(func); #if LMIC_DEBUG_LEVEL > 1 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": %s txrxFlags %#02x --> %02x\n", os_getTime(), func, LMIC.txrxFlags, mask); #endif LMIC.txrxFlags = mask; } // or the txrxFlags, with debugging static inline void orTxrxFlags(const char *func, u1_t mask) { initTxrxFlags(func, LMIC.txrxFlags | mask); } // ================================================================================ // BEG OS - default implementations for certain OS suport functions #if !defined(HAS_os_calls) #if !defined(os_rlsbf2) u2_t os_rlsbf2 (xref2cu1_t buf) { return (u2_t)((u2_t)buf[0] | ((u2_t)buf[1]<<8)); } #endif #if !defined(os_rlsbf4) u4_t os_rlsbf4 (xref2cu1_t buf) { return (u4_t)((u4_t)buf[0] | ((u4_t)buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); } #endif #if !defined(os_rmsbf4) u4_t os_rmsbf4 (xref2cu1_t buf) { return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); } #endif #if !defined(os_wlsbf2) void os_wlsbf2 (xref2u1_t buf, u2_t v) { buf[0] = v; buf[1] = v>>8; } #endif #if !defined(os_wlsbf4) void os_wlsbf4 (xref2u1_t buf, u4_t v) { buf[0] = v; buf[1] = v>>8; buf[2] = v>>16; buf[3] = v>>24; } #endif #if !defined(os_wmsbf4) void os_wmsbf4 (xref2u1_t buf, u4_t v) { buf[3] = v; buf[2] = v>>8; buf[1] = v>>16; buf[0] = v>>24; } #endif #if !defined(os_getBattLevel) u1_t os_getBattLevel (void) { return LMIC.client.devStatusAns_battery; } #endif #if !defined(os_crc16) // New CRC-16 CCITT(XMODEM) checksum for beacons: u2_t os_crc16 (xref2cu1_t data, uint len) { 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 remainder; } #endif #endif // !HAS_os_calls // END OS - default implementations for certain OS suport functions // ================================================================================ // ================================================================================ // BEG AES static void micB0 (u4_t devaddr, u4_t seqno, int dndir, int len) { os_clearMem(AESaux,16); AESaux[0] = 0x49; AESaux[5] = dndir?1:0; AESaux[15] = len; os_wlsbf4(AESaux+ 6,devaddr); os_wlsbf4(AESaux+10,seqno); } static int aes_verifyMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { micB0(devaddr, seqno, dndir, len); os_copyMem(AESkey,key,16); return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len); } static void aes_appendMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { micB0(devaddr, seqno, dndir, len); os_copyMem(AESkey,key,16); // MSB because of internal structure of AES os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len)); } static void aes_appendMic0 (xref2u1_t pdu, int len) { os_getDevKey(AESkey); os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES } static int aes_verifyMic0 (xref2u1_t pdu, int len) { os_getDevKey(AESkey); return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len); } static void aes_encrypt (xref2u1_t pdu, int len) { os_getDevKey(AESkey); os_aes(AES_ENC, pdu, len); } static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) { if( len <= 0 ) return; os_clearMem(AESaux, 16); AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1 AESaux[5] = dndir?1:0; os_wlsbf4(AESaux+ 6,devaddr); os_wlsbf4(AESaux+10,seqno); os_copyMem(AESkey,key,16); os_aes(AES_CTR, payload, len); } static void aes_sessKeys (u2_t devnonce, xref2cu1_t artnonce, xref2u1_t nwkkey, xref2u1_t artkey) { os_clearMem(nwkkey, 16); nwkkey[0] = 0x01; os_copyMem(nwkkey+1, artnonce, LEN_ARTNONCE+LEN_NETID); os_wlsbf2(nwkkey+1+LEN_ARTNONCE+LEN_NETID, devnonce); os_copyMem(artkey, nwkkey, 16); artkey[0] = 0x02; os_getDevKey(AESkey); os_aes(AES_ENC, nwkkey, 16); os_getDevKey(AESkey); os_aes(AES_ENC, artkey, 16); } // END AES // ================================================================================ // ================================================================================ // BEG LORA static CONST_TABLE(u1_t, SENSITIVITY)[7][3] = { // ------------bw---------- // 125kHz 250kHz 500kHz { 141-109, 141-109, 141-109 }, // FSK { 141-127, 141-124, 141-121 }, // SF7 { 141-129, 141-126, 141-123 }, // SF8 { 141-132, 141-129, 141-126 }, // SF9 { 141-135, 141-132, 141-129 }, // SF10 { 141-138, 141-135, 141-132 }, // SF11 { 141-141, 141-138, 141-135 } // SF12 }; int getSensitivity (rps_t rps) { return -141 + TABLE_GET_U1_TWODIM(SENSITIVITY, getSf(rps), getBw(rps)); } ostime_t calcAirTime (rps_t rps, u1_t plen) { 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 ) { 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; } else { tmp = 8; } tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */; // bw = 125000 = 15625 * 2^3 // 250000 = 15625 * 2^4 // 500000 = 15625 * 2^5 // sf = 7..12 // // osticks = tmp * OSTICKS_PER_SEC * 1< counter reduced divisor 125000/8 => 15625 // 2 => counter 2 shift on tmp 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; } // Need 32bit arithmetic for this last step return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; } // END LORA // ================================================================================ // 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 // static void setRxsyms (ostime_t rxsyms) { if (rxsyms >= (((ostime_t)1) << 10u)) { LMIC.rxsyms = (1u << 10u) - 1; } else if (rxsyms < 0) { LMIC.rxsyms = 0; } else { LMIC.rxsyms = rxsyms; } } #if !defined(DISABLE_BEACONS) 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; } 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; } rxsyms_t rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB; err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; setRxsyms(LMICbandplan_MINRX_SYMS_LoRa_ClassB + (err / dr2hsym(dr))); return (rxsyms-LMICbandplan_PAMBL_SYMS) * dr2hsym(dr) + rxoff; } // 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; } ostime_t hsym = dr2hsym(DR_BCN); LMIC.bcnRxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB + ms2osticksCeil(ms) / hsym; LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-LMICbandplan_PAMBL_SYMS) * hsym; } #endif // !DISABLE_BEACONS #if !defined(DISABLE_PING) // Setup scheduled RX window (ping/multicast slot) static void rxschedInit (xref2rxsched_t rxsched) { os_clearMem(AESkey,16); os_clearMem(LMIC.frame+8,8); os_wlsbf4(LMIC.frame, LMIC.bcninfo.time); os_wlsbf4(LMIC.frame+4, LMIC.devaddr); os_aes(AES_ENC,LMIC.frame,16); 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 rxsched->slot = 0; rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<dr); rxsched->rxsyms = LMIC.rxsyms; } static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) { again: if( rxsched->rxtime - cando >= 0 ) return 1; u1_t slot; if( (slot=rxsched->slot) >= 128 ) return 0; u1_t intv = 1<intvExp; if( (rxsched->slot = (slot += (intv))) >= 128 ) return 0; rxsched->rxtime = rxsched->rxbase + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp) - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr); rxsched->rxsyms = LMIC.rxsyms; goto again; } #endif // !DISABLE_PING) ostime_t LMICcore_rndDelay (u1_t secSpan) { u2_t r = os_getRndU2(); ostime_t delay = r; if( delay > OSTICKS_PER_SEC ) delay = r % (u2_t)OSTICKS_PER_SEC; if( secSpan > 0 ) delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC; return delay; } // delay reftime ticks, plus a random interval in [0..secSpan). static void txDelay (ostime_t reftime, u1_t secSpan) { if (secSpan != 0) reftime += LMICcore_rndDelay(secSpan); if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) { LMIC.globalDutyAvail = reftime; LMIC.opmode |= OP_RNDTX; } } void LMICcore_setDrJoin (u1_t reason, u1_t dr) { LMIC_EV_PARAMETER(reason); 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)); LMIC.datarate = dr; DO_DEVDB(LMIC.datarate,datarate); } static bit_t setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { bit_t result = 0; LMIC_EV_PARAMETER(reason); 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)); if( pow != KEEP_TXPOW && pow != LMIC.adrTxPow ) { LMIC.adrTxPow = pow; result = 1; } if( LMIC.datarate != dr ) { LMIC.datarate = dr; DO_DEVDB(LMIC.datarate,datarate); LMIC.opmode |= OP_NEXTCHNL; result = 1; } return result; } #if !defined(DISABLE_PING) void LMIC_stopPingable (void) { LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); } void LMIC_setPingable (u1_t intvExp) { // Change setting LMIC.ping.intvExp = (intvExp & 0x7); LMIC.opmode |= OP_PINGABLE; // 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); } #endif // !DISABLE_PING static void runEngineUpdate (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); engineUpdate(); } static void reportEventAndUpdate(ev_t ev) { reportEventNoUpdate(ev); engineUpdate(); } static void reportEventNoUpdate (ev_t ev) { uint32_t const evSet = UINT32_C(1) << ev; EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, e_.eui = MAIN::CDEV->getEui(), e_.info = ev)); #if LMIC_ENABLE_onEvent void (*pOnEvent)(ev_t) = onEvent; // rxstart is critical timing; legacy onEvent handlers // don't comprehend this; so don't report. if (pOnEvent != NULL && (evSet & (UINT32_C(1)<> 8; // read as signed 24-bit LMIC.bcninfo.lon = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto LMIC.bcninfo.info = d[OFF_BCN_INFO]; LMIC.bcninfo.flags |= BCN_FULL; return LMIC_BEACON_ERROR_SUCCESS_FULL; } #endif // !DISABLE_BEACONS // put a mac response to the current output buffer. Limit according to kind of // mac data (piggyback vs port 0) static bit_t put_mac_uplink_byte(uint8_t b) { if (LMIC.pendMacPiggyback) { // put in pendMacData if (LMIC.pendMacLen < sizeof(LMIC.pendMacData)) { LMIC.pendMacData[LMIC.pendMacLen++] = b; return 1; } else { return 0; } } else { // put in pendTxData if (LMIC.pendMacLen < sizeof(LMIC.pendTxData)) { LMIC.pendTxData[LMIC.pendMacLen++] = b; return 1; } else { return 0; } } } static bit_t put_mac_uplink_byte2(uint8_t b1, uint8_t b2) { u1_t outindex = LMIC.pendMacLen; if (put_mac_uplink_byte(b1) && put_mac_uplink_byte(b2)) { return 1; } else { LMIC.pendMacLen = outindex; return 0; } } static bit_t put_mac_uplink_byte3(u1_t b1, u1_t b2, u1_t b3) { u1_t outindex = LMIC.pendMacLen; if (put_mac_uplink_byte(b1) && put_mac_uplink_byte(b2) && put_mac_uplink_byte(b3)) { return 1; } else { LMIC.pendMacLen = outindex; return 0; } } static CONST_TABLE(u1_t, macCmdSize)[] = { /* 2: LinkCheckAns */ 3, /* 3: LinkADRReq */ 5, /* 4: DutyCycleReq */ 2, /* 5: RXParamSetupReq */ 5, /* 6: DevStatusReq */ 1, /* 7: NewChannelReq */ 6, /* 8: RXTimingSetupReq */ 2, /* 9: TxParamSetupReq */ 2, /* 0x0A: DlChannelReq */ 5, /* B, C: RFU */ 0, 0, /* 0x0D: DeviceTimeAns */ 6, /* 0x0E, 0x0F */ 0, 0, /* 0x10: PingSlotInfoAns */ 1, /* 0x11: PingSlotChannelReq */ 5, /* 0x12: BeaconTimingAns */ 4, /* 0x13: BeaconFreqReq */ 4 }; static u1_t getMacCmdSize(u1_t macCmd) { if (macCmd >= 2) { const unsigned macCmdMinus2 = macCmd - 2u; if (macCmdMinus2 < LENOF_TABLE(macCmdSize)) { // macCmd in table, fetch it's size. return TABLE_GET_U1(macCmdSize, macCmdMinus2); } } // macCmd too small or too large: return zero. Zero is // never a legal command size, so it signals an error // to the caller. return 0; } static bit_t applyAdrRequests( const uint8_t *opts, int olen, u1_t adrAns ) { lmic_saved_adr_state_t initialState; int const kAdrReqSize = 5; int oidx; u1_t p1 = 0; u1_t p4 = 0; bit_t response_fit = 1; bit_t map_ok = 1; LMICbandplan_saveAdrState(&initialState); // compute the changes if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { for (oidx = 0; oidx < olen; oidx += kAdrReqSize) { // can we advance? if (olen - oidx < kAdrReqSize) { // ignore the malformed one at the end break; } u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels p1 = opts[oidx+1]; // txpow + DR, in case last p4 = opts[oidx+4]; // ChMaskCtl, NbTrans u1_t chpage = p4 & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page // notice that we ignore map_ok except on the last setting. // so LMICbandplan_mapChannels should report failure status, but do // the work; if it fails, we'll back it out. map_ok = LMICbandplan_mapChannels(chpage, chmap); LMICOS_logEventUint32("applyAdrRequests: mapChannels", ((u4_t)chpage << 16)|(chmap << 0)); } } if (! map_ok) { adrAns &= ~MCMD_LinkADRAns_ChannelACK; } // p1 now has txpow + DR. DR must be feasible. dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT); if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK) && ! LMICbandplan_isDataRateFeasible(dr)) { adrAns &= ~MCMD_LinkADRAns_DataRateACK; LMICOS_logEventUint32("applyAdrRequests: final DR not feasible", dr); } if (adrAns != (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { LMICbandplan_restoreAdrState(&initialState); } // now put all the options for (oidx = 0; oidx < olen && response_fit; oidx += kAdrReqSize) { // can we advance? if (olen - oidx < kAdrReqSize) { // ignore the malformed one at the end break; } response_fit = put_mac_uplink_byte2(MCMD_LinkADRAns, adrAns); } // all done scanning options bit_t changes = LMICbandplan_compareAdrState(&initialState); // handle the final options if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { // handle uplink repeat count u1_t uprpt = p4 & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count if (LMIC.upRepeat != uprpt) { LMIC.upRepeat = uprpt; changes = 1; } LMICOS_logEventUint32("applyAdrRequests: setDrTxPow", ((u4_t)adrAns << 16)|(dr << 8)|(p1 << 0)); // handle power changes here, too. changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); } // Certification doesn't like this, but it makes the device happier with TTN. // LMIC.adrChanged = changes; // move the ADR FSM up to "time to request" return response_fit; } static int scan_mac_cmds_link_adr( const uint8_t *opts, int olen, bit_t *presponse_fit ) { LMICOS_logEventUint32("scan_mac_cmds_link_adr", olen); if (olen == 0) return 0; int oidx = 0; int const kAdrReqSize = 5; int lastOidx; u1_t adrAns = MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK; // process the contiguous slots for (;;) { lastOidx = oidx; // can we advance? if (olen - oidx < kAdrReqSize) { // ignore the malformed one at the end; but fail it. adrAns = 0; break; } 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_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page // u1_t uprpt = opts[oidx+4] & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT); if( !LMICbandplan_canMapChannels(chpage, chmap) ) { adrAns &= ~MCMD_LinkADRAns_ChannelACK; LMICOS_logEventUint32("scan_mac_cmds_link_adr: failed canMapChannels", ((u4_t)chpage << 16)|((u4_t)chmap << 0)); } if( !validDR(dr) ) { adrAns &= ~MCMD_LinkADRAns_DataRateACK; } if (pow2dBm(p1) == -128) { adrAns &= ~MCMD_LinkADRAns_PowerACK; } oidx += kAdrReqSize; if (opts[oidx] != MCMD_LinkADRReq) break; } // go back and apply the ADR changes, if any -- use the effective length, // and process. *presponse_fit = applyAdrRequests(opts, lastOidx + kAdrReqSize, adrAns); return lastOidx; } // scan mac commands starting at opts[] for olen, return count of bytes consumed. // build response in pendMacData[], but limit length as needed; simply chop at last // response that fits. static int scan_mac_cmds( const uint8_t *opts, int olen, int port ) { int oidx = 0; uint8_t cmd; LMIC.pendMacLen = 0; if (port == 0) { // port zero: mac data is in the normal payload, and there can't be // piggyback mac data. LMIC.pendMacPiggyback = 0; } else { // port is either -1 (no port) or non-zero (piggyback): treat as piggyback. LMIC.pendMacPiggyback = 1; } while( oidx < olen ) { bit_t response_fit; response_fit = 1; cmd = opts[oidx]; /* compute length, and exit for illegal commands */ // cmdlen == 0 for error, or > 0 length of command. int const cmdlen = getMacCmdSize(cmd); if (cmdlen <= 0 || cmdlen > olen - oidx) { // "the first unknown command terminates processing" olen = oidx; break; } switch( cmd ) { case MCMD_LinkCheckAns: { // TODO(tmm@mcci.com) capture these, reliably.. //int gwmargin = opts[oidx+1]; //int ngws = opts[oidx+2]; break; } // from 1.0.3 spec section 5.2: // For the purpose of configuring the end-device channel mask, the end-device will // process all contiguous LinkAdrReq messages, in the order present in the downlink message, // as a single atomic block command. The end-device will accept or reject all Channel Mask // controls in the contiguous block, and provide consistent Channel Mask ACK status // indications for each command in the contiguous block in each LinkAdrAns message, // reflecting the acceptance or rejection of this atomic channel mask setting. // // So we need to process all the contigious commands case MCMD_LinkADRReq: { // skip over all but the last command. oidx += scan_mac_cmds_link_adr(opts + oidx, olen - oidx, &response_fit); break; } case MCMD_DevStatusReq: { // LMIC.snr is SNR times 4, convert to real SNR; rounding towards zero. const int snr = (LMIC.snr + 2) / 4; // per [1.02] 5.5. the margin is the SNR. LMIC.devAnsMargin = (u1_t)(0b00111111 & (snr <= -32 ? -32 : snr >= 31 ? 31 : snr)); response_fit = put_mac_uplink_byte3(MCMD_DevStatusAns, os_getBattLevel(), LMIC.devAnsMargin); break; } #if !defined(DISABLE_MCMD_RXParamSetupReq) case MCMD_RXParamSetupReq: { dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); u1_t rx1DrOffset = (u1_t)((opts[oidx+1] & 0x70) >> 4); u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); LMIC.dn2Ans = 0xC0; // answer pending, but send this one in order. if( validDR(dr) ) LMIC.dn2Ans |= MCMD_RXParamSetupAns_RX2DataRateACK; if( freq != 0 ) LMIC.dn2Ans |= MCMD_RXParamSetupAns_ChannelACK; if (rx1DrOffset <= 3) LMIC.dn2Ans |= MCMD_RXParamSetupAns_RX1DrOffsetAck; if( LMIC.dn2Ans == (0xC0|MCMD_RXParamSetupAns_RX2DataRateACK|MCMD_RXParamSetupAns_ChannelACK| MCMD_RXParamSetupAns_RX1DrOffsetAck) ) { LMIC.dn2Dr = dr; LMIC.dn2Freq = freq; LMIC.rx1DrOffset = rx1DrOffset; DO_DEVDB(LMIC.dn2Dr,dn2Dr); DO_DEVDB(LMIC.dn2Freq,dn2Freq); } /* put the first copy of the message */ response_fit = put_mac_uplink_byte2(MCMD_RXParamSetupAns, LMIC.dn2Ans & ~MCMD_RXParamSetupAns_RFU); break; } #endif // !DISABLE_MCMD_RXParamSetupReq #if !defined(DISABLE_MCMD_RXTimingSetupReq) case MCMD_RXTimingSetupReq: { u1_t delay = opts[oidx+1] & MCMD_RXTimingSetupReq_Delay; if (delay == 0) delay = 1; LMIC.rxDelay = delay; LMIC.macRxTimingSetupAns = 2; response_fit = put_mac_uplink_byte(MCMD_RXTimingSetupAns); break; } #endif // !DISABLE_MCMD_RXTimingSetupReq #if !defined(DISABLE_MCMD_DutyCycleReq) case MCMD_DutyCycleReq: { u1_t cap = opts[oidx+1]; LMIC.globalDutyRate = cap & 0xF; LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); response_fit = put_mac_uplink_byte(MCMD_DutyCycleAns); break; } #endif // !DISABLE_MCMD_DutyCycleReq #if !defined(DISABLE_MCMD_NewChannelReq) && CFG_LMIC_EU_like case MCMD_NewChannelReq: { u1_t chidx = opts[oidx+1]; // channel u4_t raw_f_not_zero = opts[oidx+2] | opts[oidx+3] | opts[oidx+4]; u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); // freq u1_t drs = opts[oidx+5]; // datarate span u1_t ans = MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK; if (freq == 0 && raw_f_not_zero) { ans &= ~MCMD_NewChannelAns_ChannelACK; } u1_t MaxDR = drs >> 4; u1_t MinDR = drs & 0xF; if (MaxDR < MinDR || !validDR(MaxDR) || !validDR(MinDR)) { ans &= ~MCMD_NewChannelAns_DataRateACK; } if( ans == (MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK)) { if ( ! LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(MinDR, MaxDR), -1) ) { LMICOS_logEventUint32("NewChannelReq: setupChannel failed", ((u4_t)MaxDR << 24u) | ((u4_t)MinDR << 16u) | (raw_f_not_zero << 8) | (chidx << 0)); ans &= ~MCMD_NewChannelAns_ChannelACK; } } response_fit = put_mac_uplink_byte2(MCMD_NewChannelAns, ans); break; } #endif // !DISABLE_MCMD_NewChannelReq #if !defined(DISABLE_MCMD_DlChannelReq) && CFG_LMIC_EU_like case MCMD_DlChannelReq: { u1_t chidx = opts[oidx+1]; // channel u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); // freq u1_t ans = MCMD_DlChannelAns_FreqACK|MCMD_DlChannelAns_ChannelACK; if (freq == 0) { ans &= ~MCMD_DlChannelAns_ChannelACK; } if (chidx > MAX_CHANNELS) { // this is not defined by the 1.0.3 spec ans = 0; } else if ((LMIC.channelMap & (1 << chidx)) == 0) { // the channel is not enabled for downlink. ans &= ~MCMD_DlChannelAns_FreqACK; } if( ans == (MCMD_DlChannelAns_FreqACK|MCMD_DlChannelAns_ChannelACK)) { LMIC.channelDlFreq[chidx] = freq; } response_fit = put_mac_uplink_byte2(MCMD_DlChannelAns, ans); // set sticky answer. LMIC.macDlChannelAns = ans | 0xC0; break; } #endif // !DISABLE_MCMD_DlChannelReq #if !defined(DISABLE_MCMD_PingSlotChannelReq) && !defined(DISABLE_PING) case MCMD_PingSlotChannelReq: { u4_t raw_f_not_zero = opts[oidx+1] | opts[oidx+2] | opts[oidx+3]; u4_t freq = LMICbandplan_convFreq(&opts[oidx+1]); u1_t dr = opts[oidx+4] & 0xF; u1_t ans = MCMD_PingSlotFreqAns_DataRateACK|MCMD_PingSlotFreqAns_ChannelACK; if (! raw_f_not_zero) { freq = FREQ_PING; } else if (freq == 0) { ans &= ~MCMD_PingSlotFreqAns_ChannelACK; } if (! validDR(dr)) ans &= ~MCMD_PingSlotFreqAns_DataRateACK; if (ans == (MCMD_PingSlotFreqAns_DataRateACK|MCMD_PingSlotFreqAns_ChannelACK)) { LMIC.ping.freq = freq; LMIC.ping.dr = dr; DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); DO_DEVDB(LMIC.ping.freq, pingFreq); DO_DEVDB(LMIC.ping.dr, pingDr); } response_fit = put_mac_uplink_byte2(MCMD_PingSlotChannelAns, ans); break; } #endif // !DISABLE_MCMD_PingSlotChannelReq && !DISABLE_PING #if defined(ENABLE_MCMD_BeaconTimingAns) && !defined(DISABLE_BEACONS) case MCMD_BeaconTimingAns: { // Ignore if tracking already enabled or bcninfoTries == 0 if( (LMIC.opmode & OP_TRACK) == 0 && LMIC.bcninfoTries != 0) { LMIC.bcnChnl = opts[oidx+3]; // Enable tracking - bcninfoTries LMIC.opmode |= OP_TRACK; // LMIC.bcninfoTries is cleared later in txComplete handling - triggers EV_BEACON_FOUND // Setup RX parameters LMIC.bcninfo.txtime = (LMIC.rxtime + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BeaconTimingAns_TUNIT) + ms2osticksCeil(MCMD_BeaconTimingAns_TUNIT/2) - BCN_INTV_osticks); LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared) calcBcnRxWindowFromMillis(MCMD_BeaconTimingAns_TUNIT,1); // error of +/-N ms EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BeaconTimingAns, 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))); } break; } /* end case */ #endif // !ENABLE_MCMD_BeaconTimingAns && !DISABLE_BEACONS #if LMIC_ENABLE_TxParamSetupReq case MCMD_TxParamSetupReq: { uint8_t txParam; txParam = opts[oidx+1]; // we don't allow unrecognized bits to get to txParam. txParam &= (MCMD_TxParam_RxDWELL_MASK| MCMD_TxParam_TxDWELL_MASK| MCMD_TxParam_MaxEIRP_MASK); LMIC.txParam = txParam; response_fit = put_mac_uplink_byte(MCMD_TxParamSetupAns); break; } /* end case */ #endif // LMIC_ENABLE_TxParamSetupReq #if LMIC_ENABLE_DeviceTimeReq case MCMD_DeviceTimeAns: { // don't process a spurious downlink. if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_rx ) { // remember that it's time to notify the client. LMIC.txDeviceTimeReqState = lmic_RequestTimeState_success; // the network time is linked to the time of the last TX. LMIC.localDeviceTime = LMIC.txend; // save the network time. // The first 4 bytes contain the seconds since the GPS epoch // (i.e January the 6th 1980 at 00:00:00 UTC). // Note: as per the LoRaWAN specs, the octet order for all // multi-octet fields is little endian // Note: the casts are necessary, because opts is an array of // single byte values, and they might overflow when shifted LMIC.netDeviceTime = ( (lmic_gpstime_t) opts[oidx + 1] ) | (((lmic_gpstime_t) opts[oidx + 2]) << 8) | (((lmic_gpstime_t) opts[oidx + 3]) << 16) | (((lmic_gpstime_t) opts[oidx + 4]) << 24); // The 5th byte contains the fractional seconds in 2^-8 second steps LMIC.netDeviceTimeFrac = opts[oidx + 5]; #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": MAC command DeviceTimeAns received: seconds_since_gps_epoch=%"PRIu32", fractional_seconds=%d\n", os_getTime(), LMIC.netDeviceTime, LMIC.netDeviceTimeFrac); #endif } break; } /* end case */ #endif // LMIC_ENABLE_DeviceTimeReq default: { // force olen to current oidx so we'll exit the while() olen = oidx; break; } /* end case */ } /* end switch */ /* if we're out of spce for responses, skip to end. */ if (! response_fit) { olen = oidx; } else { oidx += cmdlen; } } /* end while */ return oidx; } // change the ADR ack request count, unless adr ack is diabled. static void setAdrAckCount (s2_t count) { if (LMIC.adrAckReq != LINK_CHECK_OFF) { LMIC.adrAckReq = count; } } static bit_t decodeFrame (void) { xref2u1_t d = LMIC.frame; u1_t hdr = d[0]; u1_t ftype = hdr & HDR_FTYPE; int dlen = LMIC.dataLen; #if LMIC_DEBUG_LEVEL > 0 const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); #endif if (dlen > 0) LMICOS_logEventUint32("decodeFrame", (dlen << 8) | (hdr << 0)); 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))); norx: #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Invalid downlink, window=%s\n", os_getTime(), window); #endif LMIC.dataLen = 0; return 0; } // Validate exact frame length // Note: device address was already read+evaluated in order to arrive here. int fct = d[OFF_DAT_FCT]; u4_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]); u4_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]); int olen = fct & FCT_OPTLEN; int ackup = (fct & FCT_ACK) != 0 ? 1 : 0; // ACK last up frame int poff = OFF_DAT_OPTS+olen; int pend = dlen-4; // MIC if( addr != LMIC.devaddr ) { LMICOS_logEventUint32("decodeFrame: wrong address", addr); 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 ) { LMICOS_logEventUint32("decodeFrame: corrupted frame", ((u4_t)dlen << 16) | (fct << 8) | (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; } int port = -1; int replayConf = 0; if( pend > poff ) port = d[poff++]; // compute the 32-bit sequence number based on the 16-bit sequence number received // and the internal 32-bit number. Because the 32-bit number is used in the MIC // calculation, this must be right. (And if you're curious why a 32-bit seqno matters, // it's this calculation, plus its use in the MIC calculation.) // // we have to be careful to get the right value for replay of last message received. u2_t seqnoDiff = (u2_t)(seqno - LMIC.seqnoDn); if (seqnoDiff == 0xFFFFu) { seqno = LMIC.seqnoDn - 1; } else { seqno = LMIC.seqnoDn + seqnoDiff; } if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) { LMICOS_logEventUint32("decodeFrame: bad MIC", os_rlsbf4(&d[pend])); 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 ) { 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)); LMICOS_logEventUint32("decodeFrame: rollover discarded", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); goto norx; } if( seqno != LMIC.seqnoDn-1 || !LMIC.lastDnConf || 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)); LMICOS_logEventUint32("decodeFrame: Retransmit confimed discarded", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); goto norx; } // Replay of previous sequence number allowed only if // previous frame and repeated both requested confirmation // but set a flag, so we don't actually process the message. LMICOS_logEventUint32("decodeFrame: Retransmit confimed accepted", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); replayConf = 1; LMIC.dnConf = FCT_ACK; } else { if( seqnoDiff > LMICbandplan_MAX_FCNT_GAP) { LMICOS_logEventUint32("decodeFrame: gap too big", ((u4_t)seqnoDiff << 16) | (seqno & 0xFFFFu)); goto norx; } 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 = LMIC.lastDnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); if (LMIC.dnConf) LMICOS_logEventUint32("decodeFrame: Confirmed downlink", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); } if (port == 0 && olen != 0 && pend > poff) { // we have a port-zero message, and piggyback mac data. // discard, section 4.3.1.6 line 544-546 #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": port==0 && FOptsLen=%#x: discard\n", os_getTime(), olen); #endif goto norx; } if( LMIC.dnConf || (fct & FCT_MORE) ) LMIC.opmode |= OP_POLL; // We heard from network LMIC.adrChanged = LMIC.rejoinCnt = 0; setAdrAckCount(LINK_CHECK_INIT); #if !defined(DISABLE_MCMD_RXParamSetupReq) // We heard from network "on a Class A downlink" LMIC.dn2Ans = 0; #endif // !defined(DISABLE_MCMD_RXParamSetupReq) #if !defined(DISABLE_MCMD_RXTimingSetupReq) // We heard from network "on a Class A downlink" LMIC.macRxTimingSetupAns = 0; #endif // !defined(DISABLE_MCMD_RXParamSetupReq) #if !defined(DISABLE_MCMD_DlChannelReq) && CFG_LMIC_EU_like LMIC.macDlChannelAns = 0; #endif int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps); // for legacy reasons, LMIC.margin is set to the unsigned sensitivity. It can never be negative. // it's only computed for legacy clients LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m; // even if it's a replay confirmed, we process the mac options. xref2u1_t opts = &d[OFF_DAT_OPTS]; int oidx = scan_mac_cmds(opts, olen, port); if( oidx != olen ) { EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, e_.eui = MAIN::CDEV->getEui(), e_.info = 0x1000000 + (oidx) + (olen<<8))); oidx = olen; } 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); if (port == 0) { // this is a mac command. scan the options. #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": process mac commands for port 0 (olen=%#x)\n", os_getTime(), pend-poff); #endif int optendindex = scan_mac_cmds(d+poff, pend-poff, port); if (optendindex != pend-poff) { #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF( "%"LMIC_PRId_ostime_t": error processing mac commands for port 0 " "(len=%#x, optendindex=%#x)\n", os_getTime(), pend-poff, optendindex ); #endif } // wait to transmit until txcomplete: above. } } // end decrypt payload 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)); // discard the data LMICOS_logEventUint32("decodeFrame: discarding replay", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); goto norx; } 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)); #if LMIC_DEBUG_LEVEL > 1 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": ??ack error ack=%d txCnt=%d\n", os_getTime(), ackup, LMIC.txCnt); #endif } if( LMIC.txCnt != 0 ) // we requested an ACK orTxrxFlags(__func__, ackup ? TXRX_ACK : TXRX_NACK); if( port <= 0 ) { orTxrxFlags(__func__, TXRX_NOPORT); LMIC.dataBeg = poff; LMIC.dataLen = 0; } else { orTxrxFlags(__func__, TXRX_PORT); LMIC.dataBeg = poff; LMIC.dataLen = pend-poff; } #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Received downlink, window=%s, port=%d, ack=%d, txrxFlags=%#x\n", os_getTime(), window, port, ackup, LMIC.txrxFlags); #endif return 1; } // ================================================================================ // TX/RX transaction support // start reception and log. static void radioRx (void) { reportEventNoUpdate(EV_RXSTART); os_radio(RADIO_RX); } // start RX in window 2. static void setupRx2 (void) { initTxrxFlags(__func__, TXRX_DNW2); LMIC.rps = dndr2rps(LMIC.dn2Dr); LMIC.freq = LMIC.dn2Freq; LMIC.dataLen = 0; radioRx(); } //! \brief Adjust the delay (in ticks) of the target window-open time from nominal. //! \param hsym the duration of one-half symbol in osticks. //! \param rxsyms_in the nominal window length -- minimum length of time to delay. //! \return Effective delay to use (positive for later, negative for earlier). //! \post LMIC.rxsyms is set to the number of rxsymbols to be used for preamble timeout. //! \bug For FSK, the radio driver ignores LMIC.rxsysms, and uses a fixed value of 4080 bits //! (81 ms) //! //! \details The calculation of the RX Window opening time has to balance several things. //! The system clock might be inaccurate. Generally, the LMIC assumes that the timebase //! is accurage to 100 ppm, or 0.01%. 0.01% of a 6 second window is 600 microseconds. //! For LoRa, the fastest data rates of interest is SF7 (1024 us/symbol); with an 8-byte //! preamble, the shortest preamble is 8.092ms long. If using FSK, the symbol rate is //! 20 microseconds, but the preamble is 8*5 bits long, so the preamble is 800 microseconds. //! Unless LMIC_ENABLE_arbitrary_clock_error is true, we fold clock errors of > 0.4% back //! to 0.4%. ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in) { ostime_t rxoffset; // decide if we want to move left or right of the reference time. rxoffset = -LMICbandplan_RX_EXTRA_MARGIN_osticks; u2_t clockerr = LMIC.client.clockError; // Most crystal oscillators are 100 ppm. If things are that tight, there's // no point in specifying a drift, as 6 seconds at 100ppm is +/- 600 microseconds. // We position the windows at the front, and there's some extra margin, so... // don't bother setting values <= 100 ppm. if (clockerr != 0) { // client has set clock error. Limit this to 0.1% unless there's // a compile-time configuration. (In other words, assume that millis() // clock is accurate to 0.1%.) You should never use clockerror to // compensate for system-late problems. // note about compiler: The initializer for maxError is coded for // maximum portability. On 16-bit systems, some compilers complain // if we write x / (1000 * 1000). x / 1000 / 1000 uses constants, // is generally acceptable so it can be optimized in compiler's own // way. u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / 1000 / 1000; if (! LMIC_ENABLE_arbitrary_clock_error && clockerr > maxError) { clockerr = maxError; } } // If the clock is slow, the window needs to open earlier in our time // in order to open at or before the specified time (in real world),. // Don't bother to round, as this is very fine-grained. ostime_t drift = (ostime_t)(((int64_t)delay * clockerr) / MAX_CLOCK_ERROR); // calculate the additional rxsyms needed to hit the window nominally. ostime_t const tsym = 2 * hsym; ostime_t driftwin; driftwin = 2 * drift; if (rxoffset < 0) driftwin += -rxoffset; // else we'll hit the window nominally. rxsyms_in += (driftwin + tsym - 1) / tsym; // reduce the rxoffset by the drift; this compensates for a slow clock; // but it makes the rxtime too early by approximately `drift` if clock // is fast. rxoffset -= drift; setRxsyms(rxsyms_in); return delay + rxoffset; } static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { ostime_t hsym = dr2hsym(dr); // Schedule the start of the receive window. os_getRadioRxRampup() is used to make sure we // exit "sleep" well enough in advance of the receive window to be able to // time things accurately. // // This also sets LMIC.rxsyms. This is NOT normally used for FSK; see LMICbandplan_txDoneFSK() LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - os_getRadioRxRampup()); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func); } static void setupRx1 (osjobcb_t func) { initTxrxFlags(__func__, TXRX_DNW1); // Turn LMIC.rps from TX over to RX LMIC.rps = setNocrc(LMIC.rps,1); LMIC.dataLen = 0; LMIC.osjob.func = func; radioRx(); } // 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) { #if !defined(DISABLE_PING) 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; } #endif // !DISABLE_PING // Change RX frequency (can happen even for EU-like if programmed by DlChannelReq) // change params and rps (US only) before we increment txChnl LMICbandplan_setRx1Params(); // LMIC.dndr carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) // Setup receive -- either schedule FSK or schedule rx1 or rx2 window. if( LMICbandplan_isFSK() ) { LMICbandplan_txDoneFSK(delay, func); } else { schedRx12(delay, func, LMIC.dndr); } } // ======================================== Join frames #if !defined(DISABLE_JOIN) static void onJoinFailed (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. reportEventAndUpdate(EV_JOIN_FAILED); } // process join-accept message or deal with no join-accept in slot 2. static bit_t processJoinAccept (void) { if ((LMIC.txrxFlags & TXRX_DNW1) != 0 && LMIC.dataLen == 0) return 0; // formerly we asserted. if ((LMIC.opmode & OP_TXRXPEND) == 0) // nothing we can do. return 1; // formerly we asserted. if ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) == 0) { // we shouldn't be here. just drop the frame, but clean up txrxpend. return processJoinAccept_badframe(); } if( LMIC.dataLen == 0 ) { // we didn't get any data and we're in slot 2. So... there's no join frame. return processJoinAccept_nojoinframe(); } 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! LMIC_EV_VARIABLE(mic); // only used by EV(). 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))); return processJoinAccept_badframe(); } 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)); return processJoinAccept_badframe(); } u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); LMIC.devaddr = addr; LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; // initDefaultChannels(0) for EU-like, nothing otherwise LMICbandplan_joinAcceptChannelClear(); // process the CFList if present if (dlen == LEN_JAEXT) { LMICbandplan_processJoinAcceptCFList(); } // already incremented when JOIN REQ got sent off aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], 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_.reason = ((LMIC.opmode & OP_REJOIN) != 0 ? EV::joininfo_t::REJOIN_ACCEPT : EV::joininfo_t::ACCEPT))); // // XXX(tmm@mcci.com) OP_REJOIN confuses me, and I'm not sure why we're // adjusting DRs here. We've just received a join accept, and the // datarate therefore shouldn't be in play. In effect, we set the // initial data rate based on the number of times we tried to rejoin. // if( (LMIC.opmode & OP_REJOIN) != 0 ) { #if CFG_region != LMIC_REGION_as923 // TODO(tmm@mcci.com) regionalize // Lower DR every try below current UP DR // need to check feasibility? join feasability is default. LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); #else // in the join of AS923 v1.1 or older, only DR2 (SF10) is used. // TODO(tmm@mcci.com) if the rejoin logic is at all correct, we // should be setting the uplink datarate based on the number of // tries; this doesn't set the AS923 join data rate. LMIC.datarate = AS923_DR_SF10; #endif } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); LMIC.opmode |= OP_NEXTCHNL; LMIC.txCnt = 0; stateJustJoined(); // transition to the ADR_ACK initial state. setAdrAckCount(LINK_CHECK_INIT); LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; LMIC.rx1DrOffset = (LMIC.frame[OFF_JA_DLSET] >> 4) & 0x7; LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; reportEventAndUpdate(EV_JOINED); return 1; } static bit_t processJoinAccept_badframe(void) { if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) // continue the join process: there's another window. return 0; else // stop the join process return processJoinAccept_nojoinframe(); } static bit_t processJoinAccept_nojoinframe(void) { // Valid states are JOINING (in which caise REJOIN is ignored) // or ~JOINING and REJOIN. If it's a REJOIN, // we need to turn off rejoin, signal an event, and increment // the rejoin-sent count. Internal callers will turn on rejoin // occasionally. if( (LMIC.opmode & OP_JOINING) == 0) { // formerly, we asserted ((LMIC.opmode & OP_REJOIN) != 0); // but now we just return 1 if it's not asserted. if ( (LMIC.opmode & OP_REJOIN) == 0) { LMIC.opmode &= ~OP_TXRXPEND; return 1; } LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); if( LMIC.rejoinCnt < 10 ) LMIC.rejoinCnt++; reportEventAndUpdate(EV_REJOIN_FAILED); // stop the join process. return 1; } // otherwise it's a normal join. At end of rx2, so we // need to schedule something. LMIC.opmode &= ~OP_TXRXPEND; reportEventNoUpdate(EV_JOIN_TXCOMPLETE); int failed = LMICbandplan_nextJoinState(); EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, e_.eui = MAIN::CDEV->getEui(), e_.info = LMIC.datarate|DR_PAGE, e_.info2 = failed)); // Build next JOIN REQUEST with next engineUpdate call // Optionally, report join failed. // Both after a random/chosen amount of ticks. That time // is in LMIC.txend. The delay here is either zero or 1 // tick; onJoinFailed()/runEngineUpdate() are responsible // for honoring that. XXX(tmm@mcci.com) The IBM 1.6 code // claimed to return a delay but really returns 0 or 1. // Once we update as923 to return failed after dr2, we // can take out this #if. os_setTimedCallback(&LMIC.osjob, os_getTime()+failed, failed ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed : FUNC_ADDR(runEngineUpdate)); // next step to be delayed // stop this join process. return 1; } static void processRx2Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot } // we're done with this join cycle anyway, so ignore the // result of processJoinAccept() (void) processJoinAccept(); } static void setupRx2Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); setupRx2(); } static void processRx1Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen == 0 || !processJoinAccept() ) schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); } static void setupRx1Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); setupRx1(FUNC_ADDR(processRx1Jacc)); } static void jreqDone (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); } #endif // !DISABLE_JOIN // ======================================== Data frames // Fwd decl. static bit_t processDnData(void); static void processRx2DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot // It could be that the gateway *is* sending a reply, but we // just didn't pick it up. To avoid TX'ing again while the // gateay is not listening anyway, delay the next transmission // until DNW2_SAFETY_ZONE from now, and add up to 2 seconds of // extra randomization. // BUG(tmm@mcci.com) this delay is not needed for some // regions, e.g. US915 and AU915, which have non-overlapping // uplink and downlink. txDelay(os_getTime() + DNW2_SAFETY_ZONE, 2); } processDnData(); } static void setupRx2DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processRx2DnData); setupRx2(); } static void processRx1DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen == 0 || !processDnData() ) schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); } static void setupRx1DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); setupRx1(FUNC_ADDR(processRx1DnData)); } static void updataDone (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); } // ======================================== static bit_t sendAdrAckReq(void) { if (LMIC.adrAckReq < LINK_CHECK_CONT) { return 0; } else if (LMIC.adrAckReq <= LINK_CHECK_DEAD) { return 1; } else if (LMIC.adrAckReq <= LINK_CHECK_DEAD + 32) { // for compliance, though it's not clear why they care, we stop sending requests // when we're right at the DEAD state return 0; } else if (LMIC.adrAckReq <= LINK_CHECK_UNJOIN - 32) { return 0; } else { // otherwise, if our alternative is to unjoin and we have no other info, keep // asking for a downlink. return 1; } } static bit_t buildDataFrame (void) { bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL); u1_t dlen = txdata ? LMIC.pendTxLen : 0; // Piggyback MAC options // Prioritize by importance // highest importance are the ones in the pendMac buffer. int end = OFF_DAT_OPTS; // Send piggyback data if: !txdata or txport != 0 if ((! txdata || LMIC.pendTxPort != 0) && LMIC.pendMacPiggyback && LMIC.pendMacLen != 0) { os_copyMem(LMIC.frame + end, LMIC.pendMacData, LMIC.pendMacLen); end += LMIC.pendMacLen; } LMIC.pendMacLen = 0; LMIC.pendMacPiggyback = 0; #if !defined(DISABLE_MCMD_RXParamSetupReq) // per 5.4, RxParamSetupAns is sticky. if (LMIC.dn2Ans) { if (LMIC.dn2Ans & 0x40) { LMIC.dn2Ans ^= 0x40; } else { LMIC.frame[end + 0] = MCMD_RXParamSetupAns; LMIC.frame[end + 1] = LMIC.dn2Ans & ~MCMD_RXParamSetupAns_RFU; end += 2; } } #endif // !DISABLE_MCMD_RXParamSetupReq #if !defined(DISABLE_MCMD_DlChannelReq) // per 5.4, DlChannelAns is sticky. if (LMIC.macDlChannelAns) { if (LMIC.macDlChannelAns & 0x40) { LMIC.macDlChannelAns ^= 0x40; } else { LMIC.frame[end + 0] = MCMD_DlChannelAns; LMIC.frame[end + 1] = LMIC.macDlChannelAns & ~MCMD_DlChannelAns_RFU; end += 2; } } #endif // !DISABLE_MCMD_DlChannelReq #if !defined(DISABLE_MCMD_RXTimingSetupReq) // per 5.7, RXTimingSetupAns is sticky if (LMIC.macRxTimingSetupAns == 2) { LMIC.macRxTimingSetupAns = 1; } else if (LMIC.macRxTimingSetupAns) { LMIC.frame[end++] = MCMD_RXTimingSetupAns; } #endif // !DISABLE_MCMD_RXTimingSetupReq) #if LMIC_ENABLE_DeviceTimeReq if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_tx ) { LMIC.frame[end+0] = MCMD_DeviceTimeReq; end += 1; LMIC.txDeviceTimeReqState = lmic_RequestTimeState_rx; } #endif // LMIC_ENABLE_DeviceTimeReq #if !defined(DISABLE_BEACONS) && defined(ENABLE_MCMD_BeaconTimingAns) if ( LMIC.bcninfoTries > 0 ) { LMIC.frame[end+0] = MCMD_BeaconInfoReq; end += 1; } #endif if (end > OFF_DAT_OPTS + 16) { LMICOS_logEventUint32("piggyback mac opts too long", end); return 0; } if( LMIC.adrChanged ) { // if ADR is enabled, and we were just counting down the // transmits before starting an ADR, advance the timer so // we'll do an ADR now. if (LMIC.adrAckReq < LINK_CHECK_CONT) setAdrAckCount(LINK_CHECK_CONT); LMIC.adrChanged = 0; } unsigned int flen = end + (txdata ? 5+dlen : 4); if( flen > MAX_LEN_FRAME ) { // Options and payload too big - delay payload txdata = 0; flen = end+4; } u1_t maxFlen = LMICbandplan_maxFrameLen(LMIC.datarate); if (flen > maxFlen) { LMICOS_logEventUint32("frame too long for this bandplan", ((u4_t)dlen << 16) | (flen << 8) | maxFlen); return 0; } LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled | (sendAdrAckReq() ? FCT_ADRACKReq : 0) | (end-OFF_DAT_OPTS)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); if( LMIC.txCnt == 0 && LMIC.upRepeatCount == 0 ) { LMIC.seqnoUp += 1; DO_DEVDB(LMIC.seqnoUp,seqnoUp); } else { LMICOS_logEventUint32("retransmit", ((u4_t)LMIC.frame[OFF_DAT_FCT] << 24u) | ((u4_t)LMIC.txCnt << 16u) | (LMIC.upRepeatCount << 8u) | (LMIC.upRepeat<<0u)); 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) | (LMIC.upRepeatCount << 8) | ((LMIC.datarate|DR_PAGE)<<16)))); } os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1); // Clear pending DN confirmation 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; if( LMIC.txCnt == 0 ) LMIC.txCnt = 1; } else if (LMIC.upRepeat != 0) { // we are repeating. So we need to count here. if (LMIC.upRepeatCount == 0) { LMIC.upRepeatCount = 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_.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; return 1; } #if !defined(DISABLE_BEACONS) // Callback from HAL during scan mode or when job timer expires. static void onBcnRx (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); // If we arrive via job timer make sure to put radio to rest. os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); if( LMIC.dataLen == 0 ) { // Nothing received - timeout LMIC.opmode &= ~(OP_SCAN | OP_TRACK); reportEventAndUpdate(EV_SCAN_TIMEOUT); return; } if( ! LMIC_BEACON_SUCCESSFUL(decodeBeacon()) ) { // 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 // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm calcBcnRxWindowFromMillis(13,1); LMIC.opmode &= ~OP_SCAN; // turn SCAN off LMIC.opmode |= OP_TRACK; // auto enable tracking reportEventAndUpdate(EV_BEACON_FOUND); // can be disabled in callback } // Enable receiver to listen to incoming beacons // netid defines when scan stops (any or specific beacon) // This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON // Implicitely cancels any pending TX/RX transaction. // Also cancels an onpoing joining procedure. static void startScan (void) { // formerly, we asserted. if (LMIC.devaddr == 0 || (LMIC.opmode & OP_JOINING) != 0) return; if( (LMIC.opmode & OP_SHUTDOWN) != 0 ) return; // Cancel onging TX/RX transaction LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND); LMICbandplan_setBcnRxParams(); LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx)); os_radio(RADIO_RXON); } 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 // If BCN info requested from NWK then app has to take are // of sending data up so that MCMD_BeaconInfoReq can be attached. if( (LMIC.bcninfoTries = tryBcnInfo) == 0 ) startScan(); return 1; // enabled } void LMIC_disableTracking (void) { LMIC.opmode &= ~(OP_SCAN|OP_TRACK); LMIC.bcninfoTries = 0; engineUpdate(); } #endif // !DISABLE_BEACONS // ================================================================================ // // Join stuff // // ================================================================================ #if !defined(DISABLE_JOIN) static void buildJoinRequest (u1_t ftype) { // Do not use pendTxData since we might have a pending // user level frame in there. Use RX holding area instead. xref2u1_t d = LMIC.frame; d[OFF_JR_HDR] = ftype; os_getArtEui(d + OFF_JR_ARTEUI); os_getDevEui(d + OFF_JR_DEVEUI); os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce); 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_.reason = ((LMIC.opmode & OP_REJOIN) != 0 ? EV::joininfo_t::REJOIN_REQUEST : EV::joininfo_t::REQUEST))); LMIC.dataLen = LEN_JR; LMIC.devNonce++; DO_DEVDB(LMIC.devNonce,devNonce); } static void startJoining (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); // see issue #244: for backwards compatibility // don't override what the user does after os_init(). if (LMIC.initBandplanAfterReset) LMICbandplan_resetDefaultChannels(); else LMIC.initBandplanAfterReset = 1; // let the client know that now's the time to update // network settings. reportEventAndUpdate(EV_JOINING); } // reset the joined-to-network state (and clean up) void LMIC_unjoin(void) { // reset any joining flags LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_UNJOIN); // put us in unjoined state: LMIC.devaddr = 0; // clear transmit. LMIC_clrTxData(); } // 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); LMIC.opmode &= ~OP_POLL; // Lift any previous duty limitation LMIC.globalDutyRate = 0; // Cancel scanning LMIC.opmode &= ~(OP_SCAN|OP_UNJOIN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); // Setup state LMIC.rejoinCnt = LMIC.txCnt = 0; resetJoinParams(); LMICbandplan_initJoinLoop(); LMIC.opmode |= OP_JOINING; // reportEventAndUpdate will call engineUpdate which then starts sending JOIN REQUESTS os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); return 1; } return 0; // already joined } static void unjoinAndRejoin(xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC_unjoin(); LMIC_startJoining(); } // do a deferred unjoin and rejoin, so not in engineupdate. void LMIC_unjoinAndRejoin(void) { os_setCallback(&LMIC.osjob, FUNC_ADDR(unjoinAndRejoin)); } #endif // !DISABLE_JOIN // ================================================================================ // // // // ================================================================================ #if !defined(DISABLE_PING) static void processPingRx (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen != 0 ) { initTxrxFlags(__func__, TXRX_PING); if( decodeFrame() ) { reportEventNoUpdate(EV_RXCOMPLETE); } } // Pick next ping slot engineUpdate(); } #endif // !DISABLE_PING // process downlink data at close of RX window. Return zero if another RX window // should be scheduled, non-zero to prevent scheduling of RX2 (if relevant). // Confusingly, the caller actualyl does some of the calculation, so the answer from // us is not always totaly right; the rx1 window check ignores our result unless // LMIC.datalen was non zero before calling. // // Inputs: // LMIC.dataLen number of bytes receieved; 0 --> no message at all received. // LMIC.txCnt currnt confirmed uplink count, or 0 for unconfirmed. // LMIC.txrxflags state of play for the Class A engine and message receipt. // // and many other flags in txcomplete(). // forward references. static bit_t processDnData_norx(void); static bit_t processDnData_txcomplete(void); static bit_t processDnData (void) { // if no TXRXPEND, we shouldn't be here and can do nothign. // formerly we asserted. if ((LMIC.opmode & OP_TXRXPEND) == 0) return 1; if( LMIC.dataLen == 0 ) { // if this is an RX1 window, shouldn't we return 0 to schedule // RX2? in fact, the rx1 caller ignores what we return, and // norx() doesn't call txcomplete if this is RX1. return processDnData_norx(); } // if we get here, LMIC.dataLen != 0, so there is some // traffic. else if( !decodeFrame() ) { // if we are in downlink window 1, we need to schedule // downlink window 2. if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) return 0; else // otherwise we are in downlink window 2; we will not // get any more downlink traffic from this uplink, so we need // to close the books on this uplink attempt return processDnData_norx(); } // downlink frame was accepted. This means that we're done. Except // there's one bizarre corner case. If we sent a confirmed message // and got a downlink that didn't have an ACK, we have to retry. // It is not clear why the network is permitted to do this; the // fact that they scheduled a downlink for us during one of the RX // windows is clear confirmation that the uplink made it to the // network and was valid. However, compliance checks this, so // we have to handle it and retransmit. else if (LMIC.txCnt != 0 && (LMIC.txrxFlags & TXRX_NACK) != 0) { // grr. we're confirmed but the network downlink did not // set the ACK bit. We know txCnt is non-zero, so this // will immediately fall into the retransmit path. We don't // want to do this unless it's a confirmed uplink. return processDnData_norx(); } // the transmit of the uplink is really complete. else { return processDnData_txcomplete(); } } // nothing was received this window. static bit_t processDnData_norx(void) { if( LMIC.txCnt != 0 ) { if( LMIC.txCnt < TXCONF_ATTEMPTS ) { // Per [1.0.3] section 18.4, it is recommended that the device adjust datarate down. // The spec is not clear about what should happen in case the data size is too large // for the new frame len, but it seems that we should leave theframe len at the new // data size. Therefore, we set the new data rate here, and then check at transmit time // whether the packet is now too large; if so, we abandon the transmission. LMIC.txCnt += 1; // becase txCnt was at least 1 when we entered this branch, this if() will be taken // for txCnt == 3, 5, 7. if (LMIC.txCnt & 1) { dr_t adjustedDR; // lower DR adjustedDR = decDR(LMIC.datarate); setDrTxpow(DRCHG_NOACK, adjustedDR, KEEP_TXPOW); } // TODO(tmm@mcci.com): check feasibility of lower datarate // Schedule another retransmission txDelay(LMIC.rxtime, RETRY_PERIOD_secs); LMIC.opmode &= ~OP_TXRXPEND; engineUpdate(); return 1; } // confirmed uplink is complete without an ack: no port and no flag initTxrxFlags(__func__, TXRX_NACK | TXRX_NOPORT); } else if (LMIC.upRepeatCount != 0) { if (LMIC.upRepeatCount < LMIC.upRepeat) { LMICOS_logEventUint32("processDnData: repeat", (LMIC.upRepeat<<8u) | (LMIC.upRepeatCount<<0u)); LMIC.upRepeatCount += 1; txDelay(os_getTime() + ms2osticks(LMICbandplan_TX_RECOVERY_ms), 0); LMIC.opmode &= ~OP_TXRXPEND; engineUpdate(); return 1; } // counted out: nothing received. initTxrxFlags(__func__, TXRX_NOPORT); } else { // Nothing received - implies no port initTxrxFlags(__func__, TXRX_NOPORT); } setAdrAckCount(LMIC.adrAckReq + 1); LMIC.dataBeg = LMIC.dataLen = 0; return processDnData_txcomplete(); } // this Class-A uplink-and-receive cycle is complete. static bit_t processDnData_txcomplete(void) { LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); // turn off all the repeat stuff. LMIC.txCnt = LMIC.upRepeatCount = 0; // if there's pending mac data that's not piggyback, launch it now. if (LMIC.pendMacLen != 0) { if (LMIC.pendMacPiggyback) { LMICOS_logEvent("piggyback mac message"); LMIC.opmode |= OP_POLL; // send back the mac answers even if there's no data. } else { // Every mac command on port 0 requires an uplink, if there's data. // TODO(tmm@mcci.com) -- this is why we need a queueing structure for // uplinks. // open code the logic to build this because we don't want to call // engineUpdate right now. Data is already in the uplink buffer. LMIC.pendTxConf = 0; // not confirmed LMIC.pendTxPort = 0; // port 0 LMIC.pendTxLen = LMIC.pendMacLen; LMIC.pendMacLen = 0; // discard mac data! LMIC.opmode |= OP_TXDATA; LMICOS_logEvent("port0 mac message"); } } // Half-duplex gateways can have appreciable turn-around times, // so we force a wait. It might be nice to randomize this a little, // so that armies of identical devices will not try to talk all // at once. This is potentially band-specific, so we let it come // from the band-plan files. txDelay(os_getTime() + ms2osticks(LMICbandplan_TX_RECOVERY_ms), 0); #if LMIC_ENABLE_DeviceTimeReq // // if the DeviceTimeReq FSM is active, we need to move it to idle, // completing the callback. // lmic_request_time_state_t const requestTimeState = LMIC.txDeviceTimeReqState; if ( requestTimeState != lmic_RequestTimeState_idle ) { lmic_request_network_time_cb_t * const pNetworkTimeCb = LMIC.client.pNetworkTimeCb; int flagSuccess = (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_success); LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle; if (pNetworkTimeCb != NULL) { // reset the callback, so that the user's routine // can post another request if desired. LMIC.client.pNetworkTimeCb = NULL; // call the user's notification routine. (*pNetworkTimeCb)(LMIC.client.pNetworkTimeUserData, flagSuccess); } } #endif // LMIC_ENABLE_DeviceTimeReq if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { LMIC.opmode &= ~OP_LINKDEAD; reportEventNoUpdate(EV_LINK_ALIVE); } reportEventAndUpdate(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)); dr_t newDr = decDR((dr_t)LMIC.datarate); // newDr must be feasible; there must be at least // one channel that supports the new datarate. If not, stay // at current datarate (which finalizes things). if (! LMICbandplan_isDataRateFeasible(newDr)) { LMICOS_logEventUint32("LINK_CHECK_DEAD, new DR not feasible", (newDr << 8) | LMIC.datarate); newDr = LMIC.datarate; } if( newDr == (dr_t)LMIC.datarate) { // We are already at the minimum datarate // if the link is already marked dead, we need to join. #if !defined(DISABLE_JOIN) if ( LMIC.adrAckReq > LINK_CHECK_UNJOIN ) { LMIC.opmode |= OP_UNJOIN; } #endif // !defined(DISABLE_JOIN) } else if (newDr == LORAWAN_DR0) { // the spec says: the ADRACKReq shall not be set if // the device uses its lowest available data rate. // (1.0.3, 4.3.1.1, line 458) // We let the count continue to increase. } else { // we successfully lowered the data rate... // reset so that we'll lower again after the next // 32 uplinks. setAdrAckCount(LINK_CHECK_CONT); } // Decrease DataRate and restore fullpower. setDrTxpow(DRCHG_NOADRACK, newDr, pow2dBm(0)); // be careful only to report EV_LINK_DEAD once. u2_t old_opmode = LMIC.opmode; LMIC.opmode = old_opmode | OP_LINKDEAD; if (LMIC.opmode != old_opmode) reportEventNoUpdate(EV_LINK_DEAD); // update? } #if !defined(DISABLE_BEACONS) // If this falls to zero the NWK did not answer our MCMD_BeaconInfoReq commands - try full scan if( LMIC.bcninfoTries > 0 ) { if( (LMIC.opmode & OP_TRACK) != 0 ) { reportEventNoUpdate(EV_BEACON_FOUND); // update? LMIC.bcninfoTries = 0; } else if( --LMIC.bcninfoTries == 0 ) { startScan(); // NWK did not answer - try scan } } #endif // !DISABLE_BEACONS return 1; } #if !defined(DISABLE_BEACONS) static void processBeacon (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite u1_t flags = LMIC.bcninfo.flags; ev_t ev; if( LMIC.dataLen != 0 && LMIC_BEACON_SUCCESSFUL(decodeBeacon()) ) { 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)); // formerly we'd assert on BCN_PARTIAL|BCN_FULL, but we can't get here if so } 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 too many missed beacons or we lose sync, drop back to Class A. if( LMIC.missedBcns > MAX_MISSED_BCNS || LMIC.bcnRxsyms > MAX_RXSYMS ) { LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); reportEventAndUpdate(EV_LOST_TSYNC); return; } } LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN); LMIC.bcnRxsyms = LMIC.rxsyms; rev: LMICbandplan_advanceBeaconChannel(); #if !defined(DISABLE_PING) if( (LMIC.opmode & OP_PINGINI) != 0 ) rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! #endif // !DISABLE_PING reportEventAndUpdate(ev); } // job entry: time to start receiving a beacon. static void startRxBcn (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processBeacon); radioRx(); } #endif // !DISABLE_BEACONS #if !defined(DISABLE_PING) // job entry: time to start receiving in our scheduled downlink slot. static void startRxPing (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processPingRx); radioRx(); } #endif // !DISABLE_PING // Decide what to do next for the MAC layer of a device. Inner part. // Only called from outer part. static void engineUpdate_inner (void) { #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode); #endif // Check for ongoing state: scan or TX/RX transaction if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) return; #if !defined(DISABLE_JOIN) if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { LMIC_startJoining(); return; } // we're joined but LinkTracking says we're out of luck... if ( LMIC.devaddr != 0 && (LMIC.opmode & OP_UNJOIN) != 0 ) { LMIC.opmode &= ~OP_UNJOIN; LMIC_unjoinAndRejoin(); return; } #endif // !DISABLE_JOIN ostime_t now = os_getTime(); ostime_t txbeg = 0; #if !defined(DISABLE_BEACONS) ostime_t rxtime = 0; if( (LMIC.opmode & OP_TRACK) != 0 ) { // We are tracking a beacon // formerly asserted ( now - (LMIC.bcnRxtime - os_getRadioRxRampup()) <= 0 ); rxtime = LMIC.bcnRxtime - os_getRadioRxRampup(); if (now - rxtime < 0) { // too late: drop out of Class B. LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); reportEventNoUpdate(EV_LOST_TSYNC); return; } } #endif // !DISABLE_BEACONS if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { // 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 = LMICbandplan_nextTx(now); LMIC.opmode &= ~OP_NEXTCHNL; } else { // no need to consider anything but LMIC.txend. 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 !defined(DISABLE_BEACONS) // 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; } #endif // !DISABLE_BEACONS // Earliest possible time vs overhead to setup radio if( txbeg - (now + TX_RAMPUP) < 0 ) { // We could send right now! txbeg = now; dr_t txdr = (dr_t)LMIC.datarate; #if !defined(DISABLE_JOIN) if( jacc ) { u1_t ftype; if( (LMIC.opmode & OP_REJOIN) != 0 ) { #if CFG_region != LMIC_REGION_as923 // in AS923 v1.1 or older, no need to change the datarate. // otherwise we need to check feasibility. txdr = lowerDR(txdr, LMIC.rejoinCnt); #endif } ftype = HDR_FTYPE_JREQ; buildJoinRequest(ftype); LMIC.osjob.func = FUNC_ADDR(jreqDone); } else #endif // !DISABLE_JOIN { 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; } if (! buildDataFrame()) { // can't transmit this message. Report completion. initTxrxFlags(__func__, TXRX_LENERR); if (LMIC.pendTxConf || LMIC.txCnt) { orTxrxFlags(__func__, TXRX_NACK); } LMIC.opmode &= ~(OP_POLL|OP_RNDTX|OP_TXDATA|OP_TXRXPEND); LMIC.dataBeg = LMIC.dataLen = 0; reportEventNoUpdate(EV_TXCOMPLETE); return; } LMIC.osjob.func = FUNC_ADDR(updataDone); } // end of else (not joining) 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; LMICbandplan_updateTx(txbeg); // limit power to value asked in adr LMIC.radio_txpow = LMIC.txpow > LMIC.adrTxPow ? LMIC.adrTxPow : LMIC.txpow; reportEventNoUpdate(EV_TXSTART); 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) } else { // No TX pending - no scheduled RX if( (LMIC.opmode & OP_TRACK) == 0 ) return; } #if !defined(DISABLE_BEACONS) // Are we pingable? checkrx: #if !defined(DISABLE_PING) if( (LMIC.opmode & OP_PINGINI) != 0 ) { // One more RX slot in this beacon period? if( rxschedNext(&LMIC.ping, now+os_getRadioRxRampup()) ) { 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; ostime_t rxtime_ping = LMIC.rxtime - os_getRadioRxRampup(); // did we miss the time? if (now - rxtime_ping > 0) { LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); reportEventNoUpdate(EV_LOST_TSYNC); } else { os_setTimedCallback(&LMIC.osjob, rxtime_ping, FUNC_ADDR(startRxPing)); } return; } // no - just wait for the beacon } #endif // !DISABLE_PING if( txbeg != 0 && (txbeg - rxtime) < 0 ) goto txdelay; LMICbandplan_setBcnRxParams(); LMIC.rxsyms = LMIC.bcnRxsyms; LMIC.rxtime = LMIC.bcnRxtime; if( now - rxtime >= 0 ) { LMIC.osjob.func = FUNC_ADDR(processBeacon); radioRx(); return; } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); return; #endif // !DISABLE_BEACONS 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)); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": next engine update in %"LMIC_PRId_ostime_t"\n", now, txbeg-TX_RAMPUP); os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); } // Decide what to do next for the MAC layer of a device. // Outer part. Safe to call from anywhere; defers if it // detects a recursive call. static void engineUpdate (void) { lmic_engine_update_state_t state; state = LMIC.engineUpdateState; if (state == lmic_EngineUpdateState_idle) { LMIC.engineUpdateState = lmic_EngineUpdateState_busy; do { engineUpdate_inner(); state = LMIC.engineUpdateState - 1; LMIC.engineUpdateState = state; } while (state != lmic_EngineUpdateState_idle); } else { LMIC.engineUpdateState = lmic_EngineUpdateState_again; } } void LMIC_setAdrMode (bit_t enabled) { LMIC.adrEnabled = enabled ? FCT_ADREN : 0; } // Should we have/need an ext. API like this? void LMIC_setDrTxpow (dr_t dr, s1_t txpow) { setDrTxpow(DRCHG_SET, dr, txpow); } void LMIC_shutdown (void) { os_clearCallback(&LMIC.osjob); os_radio(RADIO_RST); LMIC.opmode |= OP_SHUTDOWN; } // reset the LMIC. This is called at startup; the clear of LMIC.osjob // only works because the LMIC is guaranteed to be zero in that case. // But it's also called at frame-count rollover; in that case we have // to ensure that the user callback pointers are not clobbered. void LMIC_reset (void) { EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, e_.eui = MAIN::CDEV->getEui(), e_.info = EV_RESET)); os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); // save callback info, clear LMIC, restore. do { lmic_client_data_t client = LMIC.client; os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC)); LMIC.client = client; } while (0); // LMIC.devaddr = 0; // true from os_clearMem(). LMIC.devNonce = os_getRndU2(); LMIC.opmode = OP_NONE; LMIC.errcr = CR_4_5; LMIC.adrEnabled = FCT_ADREN; resetJoinParams(); LMIC.rxDelay = DELAY_DNW1; // LMIC.pendMacLen = 0; // LMIC.pendMacPiggyback = 0; // LMIC.dn2Ans = 0; // LMIC.macDlChannelAns = 0; // LMIC.macRxTimingSetupAns = 0; #if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; // defaults for ping LMIC.ping.dr = DR_PING; // ditto LMIC.ping.intvExp = 0xFF; #endif // !DISABLE_PING LMICbandplan_resetDefaultChannels(); DO_DEVDB(LMIC.devaddr, devaddr); DO_DEVDB(LMIC.devNonce, devNonce); DO_DEVDB(LMIC.dn2Dr, dn2Dr); DO_DEVDB(LMIC.dn2Freq, dn2Freq); #if !defined(DISABLE_PING) DO_DEVDB(LMIC.ping.freq, pingFreq); DO_DEVDB(LMIC.ping.dr, pingDr); DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); #endif // !DISABLE_PING #if LMIC_ENABLE_DeviceTimeReq LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle; LMIC.netDeviceTime = 0; // the "invalid" time. LMIC.netDeviceTimeFrac = 0; #endif // LMIC_ENABLE_DeviceTimeReq } void LMIC_init (void) { LMIC.opmode = OP_SHUTDOWN; LMIC.client.devStatusAns_battery = MCMD_DEVS_BATT_NOINFO; LMICbandplan_init(); } void LMIC_clrTxData (void) { u2_t opmode = LMIC.opmode; bit_t const txActive = opmode & OP_TXDATA; if (! txActive) { return; } LMIC.pendTxLen = 0; opmode &= ~(OP_TXDATA | OP_POLL); if (! (opmode & OP_JOINING)) { // in this case, we are joining, and the TX data // is just pending. opmode &= ~(OP_TXRXPEND); } LMIC.opmode = opmode; if (txActive) reportEventNoUpdate(EV_TXCANCELED); if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING return; os_clearCallback(&LMIC.osjob); os_radio(RADIO_RST); engineUpdate(); } dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) { if (payloadSize > MAX_LEN_PAYLOAD) { return dr; } const u1_t frameSize = payloadSize + OFF_DAT_OPTS + 5; dr_t trialDr, nextDr; for (trialDr = dr; ;) { if (! LMICbandplan_isDataRateFeasible(trialDr)) break; u1_t maxSizeThisDr = LMICbandplan_maxFrameLen(trialDr); if (maxSizeThisDr == 0) { break; } else if (frameSize <= maxSizeThisDr) { // we found one that is feasible! return trialDr; } // try the next DR nextDr = incDR(trialDr); if (nextDr == trialDr) break; trialDr = nextDr; } // if we get here, we didn't find a working dr. return dr; } static bit_t isTxPathBusy(void) { return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0; } bit_t LMIC_queryTxReady (void) { return ! isTxPathBusy(); } static bit_t adjustDrForFrameIfNotBusy(u1_t len) { if (isTxPathBusy()) { return 0; } dr_t newDr = LMIC_feasibleDataRateForFrame(LMIC.datarate, len); if (newDr != LMIC.datarate) { setDrTxpow(DRCHG_FRAMESIZE, newDr, KEEP_TXPOW); } return 1; } void LMIC_setTxData (void) { adjustDrForFrameIfNotBusy(LMIC.pendTxLen); LMIC_setTxData_strict(); } void LMIC_setTxData_strict (void) { if (isTxPathBusy()) { return; } LMICOS_logEventUint32(__func__, ((u4_t)LMIC.pendTxPort << 24u) | ((u4_t)LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u)); LMIC.opmode |= OP_TXDATA; if( (LMIC.opmode & OP_JOINING) == 0 ) { LMIC.txCnt = 0; // reset the confirmed uplink FSM LMIC.upRepeatCount = 0; // reset the unconfirmed repeat FSM } engineUpdate(); } // send a message, attempting to adjust TX data rate lmic_tx_error_t LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { adjustDrForFrameIfNotBusy(dlen); return LMIC_setTxData2_strict(port, data, dlen, confirmed); } // send a message w/o callback; do not adjust data rate lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { if (isTxPathBusy()) { // already have a message queued return LMIC_ERROR_TX_BUSY; } if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) return LMIC_ERROR_TX_TOO_LARGE; if( data != (xref2u1_t)0 ) os_copyMem(LMIC.pendTxData, data, dlen); LMIC.pendTxConf = confirmed; LMIC.pendTxPort = port; LMIC.pendTxLen = dlen; LMIC_setTxData_strict(); if ( (LMIC.opmode & OP_TXDATA) == 0 ) { if (LMIC.txrxFlags & TXRX_LENERR) { return LMIC_ERROR_TX_NOT_FEASIBLE; } else { // data has already been completed with error for some reason return LMIC_ERROR_TX_FAILED; } } return LMIC_ERROR_SUCCESS; } // send a message with callback; try to adjust data rate lmic_tx_error_t LMIC_sendWithCallback ( u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData ) { adjustDrForFrameIfNotBusy(dlen); return LMIC_sendWithCallback_strict(port, data, dlen, confirmed, pCb, pUserData); } // send a message with callback; do not adjust datarate lmic_tx_error_t LMIC_sendWithCallback_strict ( u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData ) { lmic_tx_error_t const result = LMIC_setTxData2_strict(port, data, dlen, confirmed); if (result == 0) { LMIC.client.txMessageCb = pCb; LMIC.client.txMessageUserData = pUserData; } return result; } // Send a payload-less message to signal device is alive void LMIC_sendAlive (void) { LMIC.opmode |= OP_POLL; engineUpdate(); } // Check if other networks are around. void LMIC_tryRejoin (void) { LMIC.opmode |= OP_REJOIN; engineUpdate(); } //! \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. // TODO(tmm@mcci.com) we ought to also save the channels that were returned by the // join accept; right now this has to be done by the caller (or it doesn't get done). void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey) { LMIC.netid = netid; LMIC.devaddr = devaddr; if( nwkKey != (xref2u1_t)0 ) os_copyMem(LMIC.nwkKey, nwkKey, 16); if( artKey != (xref2u1_t)0 ) os_copyMem(LMIC.artKey, artKey, 16); LMICbandplan_setSessionInitDefaultChannels(); LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_UNJOIN|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); LMIC.opmode |= OP_NEXTCHNL; stateJustJoined(); // transition to the ADR_ACK_DELAY state. setAdrAckCount(LINK_CHECK_CONT); DO_DEVDB(LMIC.netid, netid); DO_DEVDB(LMIC.devaddr, devaddr); DO_DEVDB(LMIC.nwkKey, nwkkey); DO_DEVDB(LMIC.artKey, artkey); DO_DEVDB(LMIC.seqnoUp, seqnoUp); DO_DEVDB(LMIC.seqnoDn, seqnoDn); } // 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; } // Sets the max clock error to compensate for (defaults to 0, which // allows for +/- 640 at SF7BW250). MAX_CLOCK_ERROR represents +/-100%, // so e.g. for a +/-1% error you would pass MAX_CLOCK_ERROR * 1 / 100. void LMIC_setClockError(u2_t error) { LMIC.client.clockError = error; } // \brief return the uplink sequence number for the next transmission. // This simple getter returns the uplink sequence number maintained by the LMIC engine. // The caller should store the value and restore it (see LMIC_setSeqnoUp) on // LMIC initialization to ensure monotonically increasing sequence numbers. // It's also useful in debugging, as it allows you to correlate a debug trace event with // a specific packet sent over the air. u4_t LMIC_getSeqnoUp(void) { return LMIC.seqnoUp; } // \brief set the uplink sequence number for the next transmission. // Use the function on startup to ensure that the next transmission uses // a sequence number higher than the last transmission. u4_t LMIC_setSeqnoUp(u4_t seq_no) { u4_t last = LMIC.seqnoUp; LMIC.seqnoUp = seq_no; return last; } // \brief return the current session keys returned from join. void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xref2u1_t artKey) { *netid = LMIC.netid; *devaddr = LMIC.devaddr; memcpy(artKey, LMIC.artKey, sizeof(LMIC.artKey)); memcpy(nwkKey, LMIC.nwkKey, sizeof(LMIC.nwkKey)); } // \brief post an asynchronous request for the network time. void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void *pUserData) { #if LMIC_ENABLE_DeviceTimeReq if (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_idle) { LMIC.txDeviceTimeReqState = lmic_RequestTimeState_tx; LMIC.client.pNetworkTimeCb = pCallbackfn; LMIC.client.pNetworkTimeUserData = pUserData; return; } #endif // LMIC_ENABLE_DeviceTimeReq // if no device time support, or if not in proper state, // report a failure. if (pCallbackfn != NULL) (*pCallbackfn)(pUserData, /* false */ 0); } // \brief return local/remote time pair (if valid, and DeviceTimeReq enabled), // return true for success, false for error. We adjust the sampled OS time // back in time to the nearest second boundary. int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference) { #if LMIC_ENABLE_DeviceTimeReq if (pReference != NULL && // valid parameter, and LMIC.netDeviceTime != 0) { // ... we have a reasonable answer. const ostime_t tAdjust = LMIC.netDeviceTimeFrac * ms2osticks(1000) / 256; pReference->tLocal = LMIC.localDeviceTime - tAdjust; pReference->tNetwork = LMIC.netDeviceTime; return 1; } #else LMIC_API_PARAMETER(pReference); #endif // LMIC_ENABLE_DeviceTimeReq return 0; } /// /// \brief set battery level to be returned by `DevStatusAns`. /// /// \param uBattLevel is the 8-bit value to be returned. Per LoRaWAN 1.0.3 line 769, /// this is \c MCMD_DEVS_EXT_POWER (0) if on external power, /// \c MCMD_DEVS_NOINFO (255) if not able to measure battery level, /// or a value in [ \c MCMD_DEVS_BATT_MIN, \c MCMD_DEVS_BATT_MAX ], numerically /// [1, 254], to represent the charge state of the battery. Note that /// this is not millivolts. /// /// \returns /// This function returns the previous value of the battery level. /// /// \details /// The LMIC maintains an idea of the current battery state, initially set to /// \c MCMD_DEVS_NOINFO after the call to LMIC_init(). The appplication then calls /// this function from time to time to update the battery level. /// /// It is possible (in non-Arduino environments) to supply a local implementation /// of os_getBatteryLevel(). In that case, it's up to the implementation to decide /// whether to use the value supplied by this API. /// /// This implementation was chosen to minimize the risk of a battery measurement /// introducting breaking delays into the LMIC. /// u1_t LMIC_setBatteryLevel(u1_t uBattLevel) { const u1_t result = LMIC.client.devStatusAns_battery; LMIC.client.devStatusAns_battery = uBattLevel; return result; } /// /// \brief get battery level that is to be returned by `DevStatusAns`. /// /// \returns /// This function returns the saved value of the battery level. /// /// \see LMIC_setBatteryLevel() /// u1_t LMIC_getBatteryLevel(void) { return LMIC.client.devStatusAns_battery; }