/* * Copyright (c) 2014-2016 IBM 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 #include "lmic.h" #if !defined(MINRX_SYMS) #define MINRX_SYMS 5 #endif // !defined(MINRX_SYMS) #define PAMBL_SYMS 8 #define PAMBL_FSK 5 #define PRERX_FSK 1 #define RXLEN_FSK (1+5+2) #define BCN_INTV_osticks sec2osticks(BCN_INTV_sec) #define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms) #define JOIN_GUARD_osticks ms2osticks(JOIN_GUARD_ms) #define DELAY_DNW1_osticks sec2osticks(DELAY_DNW1) #define DELAY_DNW2_osticks sec2osticks(DELAY_DNW2) #define DELAY_JACC1_osticks sec2osticks(DELAY_JACC1) #define DELAY_JACC2_osticks sec2osticks(DELAY_JACC2) #define DELAY_EXTDNW2_osticks sec2osticks(DELAY_EXTDNW2) #define BCN_RESERVE_osticks ms2osticks(BCN_RESERVE_ms) #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; // Fwd decls. static void engineUpdate(void); static void startScan (void); // ================================================================================ // 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)(buf[0] | (buf[1]<<8)); } #endif #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 #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 #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 MCMD_DEVS_BATT_NOINFO; } #endif #if !defined(os_crc16) // New CRC-16 CCITT(XMODEM) checksum for beacons: u2_t os_crc16 (xref2u1_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 #if defined(CFG_eu868) // ======================================== #define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF) const u1_t maxFrameLens [] = { 64,64,64,123 }; const u1_t _DR2RPS_CRC[] = { ILLEGAL_RPS, (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0), (u1_t)MAKERPS(SF7, BW250, CR_4_5, 0, 0), (u1_t)MAKERPS(FSK, BW125, CR_4_5, 0, 0), ILLEGAL_RPS }; static const s1_t TXPOWLEVELS[] = { 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0 }; #define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT]) #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 }; const u1_t _DR2RPS_CRC[] = { ILLEGAL_RPS, MAKERPS(SF10, BW125, CR_4_5, 0, 0), MAKERPS(SF9 , BW125, CR_4_5, 0, 0), MAKERPS(SF8 , BW125, CR_4_5, 0, 0), MAKERPS(SF7 , BW125, CR_4_5, 0, 0), MAKERPS(SF8 , BW500, CR_4_5, 0, 0), ILLEGAL_RPS , ILLEGAL_RPS , ILLEGAL_RPS , MAKERPS(SF12, BW500, CR_4_5, 0, 0), MAKERPS(SF11, BW500, CR_4_5, 0, 0), MAKERPS(SF10, BW500, CR_4_5, 0, 0), MAKERPS(SF9 , BW500, CR_4_5, 0, 0), MAKERPS(SF8 , BW500, CR_4_5, 0, 0), MAKERPS(SF7 , BW500, CR_4_5, 0, 0), ILLEGAL_RPS }; #define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1))) #endif // ================================================ static const 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 + 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; } extern inline rps_t updr2rps (dr_t dr); extern inline rps_t dndr2rps (dr_t dr); extern inline int isFasterDR (dr_t dr1, dr_t dr2); extern inline int isSlowerDR (dr_t dr1, dr_t dr2); extern inline dr_t incDR (dr_t dr); extern inline dr_t decDR (dr_t dr); extern inline dr_t assertDR (dr_t dr); extern inline dr_t validDR (dr_t dr); extern inline dr_t lowerDR (dr_t dr, u1_t n); extern inline sf_t getSf (rps_t params); extern inline rps_t setSf (rps_t params, sf_t sf); extern inline bw_t getBw (rps_t params); extern inline rps_t setBw (rps_t params, bw_t cr); extern inline cr_t getCr (rps_t params); extern inline rps_t setCr (rps_t params, cr_t cr); extern inline int getNocrc (rps_t params); extern inline rps_t setNocrc (rps_t params, int nocrc); extern inline int getIh (rps_t params); extern inline rps_t setIh (rps_t params, int ih); extern inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); extern inline int sameSfBw (rps_t r1, rps_t r2); // END LORA // ================================================================================ // Adjust DR for TX retries // - indexed by retry count // - return steps to lower DR static const u1_t DRADJUST[2+TXCONF_ATTEMPTS] = { // normal frames - 1st try / no retry 0, // confirmed frames 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 // // Times for half symbol per DR // Per DR table to minimize rounding errors static const ostime_t DR2HSYM_osticks[] = { #if defined(CFG_eu868) #define dr2hsym(dr) (DR2HSYM_osticks[(dr)]) us2osticksRound(128<<7), // DR_SF12 us2osticksRound(128<<6), // DR_SF11 us2osticksRound(128<<5), // DR_SF10 us2osticksRound(128<<4), // DR_SF9 us2osticksRound(128<<3), // DR_SF8 us2osticksRound(128<<2), // DR_SF7 us2osticksRound(128<<1), // DR_SF7B us2osticksRound(80) // FSK -- not used (time for 1/2 byte) #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 us2osticksRound(128<<3), // DR_SF8 DR_SF10CR us2osticksRound(128<<2), // DR_SF7 DR_SF9CR us2osticksRound(128<<1), // DR_SF8C DR_SF8CR us2osticksRound(128<<0) // ------ DR_SF7CR #endif }; 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; } u1_t rxsyms = MINRX_SYMS; err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr)); return (rxsyms-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 = MINRX_SYMS + ms2osticksCeil(ms) / hsym; LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym; } // 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; } static ostime_t 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; } 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; } } 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)); LMIC.datarate = 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)); if( pow != KEEP_TXPOW ) LMIC.adrTxPow = pow; if( LMIC.datarate != dr ) { LMIC.datarate = dr; DO_DEVDB(LMIC.datarate,datarate); LMIC.opmode |= OP_NEXTCHNL; } } 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); } #if defined(CFG_eu868) // ================================================================================ // // BEG: EU868 related stuff // enum { NUM_DEFAULT_CHANNELS=6 }; static const u4_t iniChannelFreq[12] = { // Join frequencies and duty cycle limit (0.1%) 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) { os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); LMIC.channelMap = 0x3F; u1_t su = join ? 0 : 6; for( u1_t fu=0; fu<6; fu++,su++ ) { LMIC.channelFreq[fu] = iniChannelFreq[su]; LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7); } 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_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]; xref2band_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<> 8) * 100; if( freq < EU868_FREQ_MIN || freq > EU868_FREQ_MAX ) freq = 0; return freq; } static u1_t mapChannels (u1_t chpage, u2_t chmap) { // 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; chnltxpow; band->avail = txbeg + airtime * band->txcap; if( LMIC.globalDutyRate != 0 ) LMIC.globalDutyAvail = txbeg + (airtime< 0 ) mintime = LMIC.bands[band = bi].avail; } // Find next channel in given band u1_t chnl = LMIC.bands[band].lastchnl; for( u1_t ci=0; ci= MAX_CHANNELS ) chnl -= MAX_CHANNELS; if( (LMIC.channelMap & (1<>LMIC.datarate)); // 1 - triggers EV_JOIN_FAILED event return failed; } // // END: EU868 related stuff // // ================================================================================ #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[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; return freq; } 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 chidx -= 72; LMIC.xchFreq[chidx] = freq; 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; } else { if( chpage >= (72+MAX_XCHANNELS+15)/16 ) return 0; LMIC.channelMap[chpage] = chmap; } return 1; } 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.txpow = 26; if( chnl < 64+8 ) { LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP; } else { 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<= 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; } } } // 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.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN); } #define setRx1Params() { \ LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ 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.chRnd = 0; LMIC.txChnl = 0; LMIC.adrTxPow = 20; ASSERT((LMIC.opmode & OP_NEXTCHNL)==0); LMIC.txend = os_getTime(); setDrJoin(DRCHG_SET, DR_SF7); } static ostime_t nextJoinState (void) { // Try the following: // SF7/8/9/10 on a random channel 0..63 // SF8C on a random channel 64..71 // u1_t failed = 0; if( LMIC.datarate != DR_SF8C ) { LMIC.txChnl = 64+(LMIC.txChnl&7); setDrJoin(DRCHG_SET, DR_SF8C); } else { 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); } 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; } // // END: US915 related stuff // // ================================================================================ #else #error Unsupported frequency band! #endif static void runEngineUpdate (xref2osjob_t osjob) { engineUpdate(); } static void reportEvent (ev_t ev) { EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_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; LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0; LMIC.moreData = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0; LMIC.pingSetAns = 0; LMIC.upRepeat = 0; LMIC.adrAckReq = LINK_CHECK_INIT; LMIC.dn2Dr = DR_DNW2; LMIC.dn2Freq = FREQ_DNW2; LMIC.bcnChnl = CHNL_BCN; LMIC.ping.freq = FREQ_PING; LMIC.ping.dr = DR_PING; } // ================================================================================ // Decoding frames // Decode beacon - do not overwrite bcninfo unless we have a match! static int decodeBeacon (void) { ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this xref2u1_t d = LMIC.frame; 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 LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL); // Match - update bcninfo structure 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; // 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 LMIC.bcninfo.info = d[OFF_BCN_INFO]; LMIC.bcninfo.flags |= BCN_FULL; return 2; } 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( 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: 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 ) { 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; } int port = -1; int replayConf = 0; if( pend > 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(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)); 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(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; // We heard from network LMIC.adrChanged = LMIC.rejoinCnt = 0; if( LMIC.adrAckReq != LINK_CHECK_OFF ) LMIC.adrAckReq = LINK_CHECK_INIT; // Process OPTS 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 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; 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; } 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; } 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; } 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))); } 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; } if( oidx != olen ) { 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); 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)); } 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.txCnt != 0 ) // we requested an ACK LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; if( port < 0 ) { LMIC.txrxFlags |= TXRX_NOPORT; LMIC.dataBeg = poff; LMIC.dataLen = 0; } else { LMIC.txrxFlags |= TXRX_PORT; LMIC.dataBeg = poff; LMIC.dataLen = pend-poff; } return 1; } // ================================================================================ // TX/RX transaction support static void setupRx2 (void) { LMIC.txrxFlags = TXRX_DNW2; LMIC.rps = dndr2rps(LMIC.dn2Dr); LMIC.freq = LMIC.dn2Freq; LMIC.dataLen = 0; os_radio(RADIO_RX); } static void schedRx2 (ostime_t delay, osjobcb_t func) { // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble. LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dn2Dr); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } static void setupRx1 (osjobcb_t func) { LMIC.txrxFlags = TXRX_DNW1; // Turn LMIC.rps from TX over to RX LMIC.rps = setNocrc(LMIC.rps,1); LMIC.dataLen = 0; LMIC.osjob.func = func; os_radio(RADIO_RX); } // 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( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) { 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 defined(CFG_eu868) if( /* TX datarate */LMIC.rxsyms == DR_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(LMIC.dndr); LMIC.rxsyms = MINRX_SYMS; } os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } // ======================================== Join frames static void onJoinFailed (xref2osjob_t osjob) { // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. reportEvent(EV_JOIN_FAILED); } static bit_t processJoinAccept (void) { ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0); ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); if( LMIC.dataLen == 0 ) { nojoinframe: 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; } 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))); badframe: 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; } u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); LMIC.devaddr = addr; LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; #if defined(CFG_eu868) initDefaultChannels(0); #endif if( dlen > LEN_JA ) { #if defined(CFG_us915) goto badframe; #endif dlen = OFF_CFLIST; for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { u4_t freq = convFreq(&LMIC.frame[dlen]); if( freq ) LMIC_setupChannel(chidx, freq, 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(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))); ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0); if( (LMIC.opmode & OP_REJOIN) != 0 ) { // 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(); reportEvent(EV_JOINED); return 1; } static void processRx2Jacc (xref2osjob_t osjob) { if( LMIC.dataLen == 0 ) 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)); } static void jreqDone (xref2osjob_t osjob) { txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); } // ======================================== Data frames // 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 // 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)), FUNC_ADDR(processRx2DnDataDelay)); return; } processDnData(); } static void setupRx2DnData (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processRx2DnData); setupRx2(); } static void processRx1DnData (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processDnData() ) schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); } static void setupRx1DnData (xref2osjob_t osjob) { setupRx1(FUNC_ADDR(processRx1DnData)); } static void updataDone (xref2osjob_t osjob) { txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData)); } // ======================================== static void 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 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; } if( LMIC.dutyCapAns ) { 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; } 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; } 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; } if( LMIC.bcninfoTries > 0 ) { LMIC.frame[end] = MCMD_BCNI_REQ; end += 1; } if( LMIC.adrChanged ) { 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; } if( LMIC.snchAns ) { 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; } 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)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); if( LMIC.txCnt == 0 ) { 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)))); } 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; } 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; } // Callback from HAL during scan mode or when job timer expires. static void onBcnRx (xref2osjob_t job) { // 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); 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; } // 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 reportEvent(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) { ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0); 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); 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_BCNI_REQ 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(); } // ================================================================================ // // Join stuff // // ================================================================================ 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) { reportEvent(EV_JOINING); } // 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); // 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; // already joined } // ================================================================================ // // // // ================================================================================ static void processPingRx (xref2osjob_t osjob) { if( LMIC.dataLen != 0 ) { LMIC.txrxFlags = TXRX_PING; if( decodeFrame() ) { reportEvent(EV_RXCOMPLETE); return; } } // Pick next ping slot engineUpdate(); } static bit_t processDnData (void) { ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); if( LMIC.dataLen == 0 ) { norx: 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; } 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; LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; 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; } else if( --LMIC.bcninfoTries == 0 ) { startScan(); // NWK did not answer - try scan } } return 1; } if( !decodeFrame() ) { if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) return 0; goto norx; } goto txcomplete; } static void processBeacon (xref2osjob_t 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 && 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_.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.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! reportEvent(ev); } static void startRxBcn (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processBeacon); os_radio(RADIO_RX); } static void startRxPing (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processPingRx); os_radio(RADIO_RX); } // Decide what to do next for the MAC layer of a device static void engineUpdate (void) { // Check for ongoing state: scan or TX/RX transaction if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) return; if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { LMIC_startJoining(); return; } ostime_t now = os_getTime(); ostime_t rxtime = 0; 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; } 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! txbeg = 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); reportEvent(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; } // 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 } if( txbeg != 0 && (txbeg - rxtime) < 0 ) 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; } 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)); os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); } 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; } 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); 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 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); } void LMIC_init (void) { LMIC.opmode = OP_SHUTDOWN; } void LMIC_clrTxData (void) { 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; os_clearCallback(&LMIC.osjob); os_radio(RADIO_RST); engineUpdate(); } void LMIC_setTxData (void) { LMIC.opmode |= OP_TXDATA; if( (LMIC.opmode & OP_JOINING) == 0 ) LMIC.txCnt = 0; // cancel any ongoing TX/RX retries engineUpdate(); } // int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) return -2; if( data != (xref2u1_t)0 ) os_copyMem(LMIC.pendTxData, data, dlen); LMIC.pendTxConf = confirmed; LMIC.pendTxPort = port; LMIC.pendTxLen = dlen; LMIC_setTxData(); return 0; } // 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. 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); #if defined(CFG_eu868) initDefaultChannels(0); #endif LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); LMIC.opmode |= OP_NEXTCHNL; stateJustJoined(); 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; }