From 74cba7ea6963d28a0c7cb67b390a060e92b3ddc1 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 3 Feb 2016 14:25:36 +0100 Subject: [PATCH 01/27] Reduce the default channel list By default, LMIC uses 6 standard channels for joining and 3 standard channels for normal operation which are defined in the LoRaWAN specification. On top of that, it defines 3 additional channels. However, in practice it turns out that 3 of the 6 joining channels are not actually used by gateway, and neither are the 3 additional channels for normal operation. To maximize default operability, this commit reduces the default channels list to just (the same) 3 channels for both joining and normal operation (but at different duty cycles). Extra channels can be configured from the sketch, using LMIC_setupChannel(), and the example sketches will be modifed according to this. --- lmic/lmic.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 9867cce..7a2ebe2 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -542,15 +542,12 @@ void LMIC_setPingable (u1_t intvExp) { // // BEG: EU868 related stuff // -enum { NUM_DEFAULT_CHANNELS=6 }; -static CONST_TABLE(u4_t, iniChannelFreq)[12] = { +enum { NUM_DEFAULT_CHANNELS=3 }; +static CONST_TABLE(u4_t, iniChannelFreq)[6] = { // 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, + EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|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) { @@ -558,16 +555,12 @@ static void initDefaultChannels (bit_t join) { 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.channelMap = 0x07; + u1_t su = join ? 0 : 3; + for( u1_t fu=0; fu<3; fu++,su++ ) { LMIC.channelFreq[fu] = TABLE_GET_U4(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; From 45501eae307aa9410bf89b892f29550826bad75e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 4 Feb 2016 17:53:49 +0100 Subject: [PATCH 02/27] Allow disabling some features This allows disabling joining (over the air activation), beacon tracking and ping reception. Disabling these shrinks the code by a fair bit, so this is interesting for low-memory applications that do not need these things. This can also disable processing of some MAC commands. They are silently ignored, but they are recognized and skipped, so MAC commands coming after them can still be processed. --- lmic/lmic.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++-- lmic/lmic.h | 20 ++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 7a2ebe2..3dedc9c 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -28,6 +28,10 @@ //! \file #include "lmic.h" +#if defined(DISABLE_BEACONS) && !defined(DISABLE_PING) +#error Ping needs beacon tracking +#endif + #if !defined(MINRX_SYMS) #define MINRX_SYMS 5 #endif // !defined(MINRX_SYMS) @@ -402,6 +406,7 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { }; +#if !defined(DISABLE_BEACONS) static ostime_t calcRxWindow (u1_t secs, dr_t dr) { ostime_t rxoff, err; if( secs==0 ) { @@ -433,8 +438,10 @@ static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) { LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-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); @@ -469,6 +476,7 @@ static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) { rxsched->rxsyms = LMIC.rxsyms; goto again; } +#endif // !DISABLE_PING) static ostime_t rndDelay (u1_t secSpan) { @@ -521,6 +529,7 @@ static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { } +#if !defined(DISABLE_PING) void LMIC_stopPingable (void) { LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); } @@ -536,6 +545,7 @@ void LMIC_setPingable (u1_t intvExp) { LMIC_enableTracking(0); } +#endif // !DISABLE_PING #if defined(CFG_eu868) // ================================================================================ @@ -676,14 +686,17 @@ static ostime_t nextTx (ostime_t now) { } +#if !defined(DISABLE_BEACONS) static void setBcnRxParams (void) { LMIC.dataLen = 0; LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3; LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN); } +#endif // !DISABLE_BEACONS #define setRx1Params() /*LMIC.freq/rps remain unchanged*/ +#if !defined(DISABLE_JOIN) static void initJoinLoop (void) { #if CFG_TxContinuousMode LMIC.txChnl = 0; @@ -729,6 +742,7 @@ static ostime_t nextJoinState (void) { // 1 - triggers EV_JOIN_FAILED event return failed; } +#endif // !DISABLE_JOIN // // END: EU868 related stuff @@ -830,11 +844,13 @@ static void _nextTx (void) { // No feasible channel found! Keep old one. } +#if !defined(DISABLE_BEACONS) 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); } +#endif // !DISABLE_BEACONS #define setRx1Params() { \ LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ @@ -845,6 +861,7 @@ static void setBcnRxParams (void) { LMIC.rps = dndr2rps(LMIC.dndr); \ } +#if !defined(DISABLE_JOIN) static void initJoinLoop (void) { LMIC.chRnd = 0; LMIC.txChnl = 0; @@ -883,6 +900,7 @@ static ostime_t nextJoinState (void) { // 1 - triggers EV_JOIN_FAILED event return failed; } +#endif // !DISABLE_JOIN // // END: US915 related stuff @@ -910,7 +928,9 @@ static void reportEvent (ev_t ev) { static void runReset (xref2osjob_t osjob) { // Disable session LMIC_reset(); +#if !defined(DISABLE_JOIN) LMIC_startJoining(); +#endif // !DISABLE_JOIN reportEvent(EV_RESET); } @@ -919,14 +939,20 @@ static void stateJustJoined (void) { LMIC.rejoinCnt = 0; LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0; LMIC.moreData = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0; +#if !defined(DISABLE_PING) LMIC.pingSetAns = 0; +#endif LMIC.upRepeat = 0; LMIC.adrAckReq = LINK_CHECK_INIT; LMIC.dn2Dr = DR_DNW2; LMIC.dn2Freq = FREQ_DNW2; +#if !defined(DISABLE_BEACONS) LMIC.bcnChnl = CHNL_BCN; +#endif +#if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; LMIC.ping.dr = DR_PING; +#endif } @@ -934,6 +960,7 @@ static void stateJustJoined (void) { // Decoding frames +#if !defined(DISABLE_BEACONS) // 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 @@ -969,6 +996,7 @@ static int decodeBeacon (void) { LMIC.bcninfo.flags |= BCN_FULL; return 2; } +#endif // !DISABLE_BEACONS static bit_t decodeFrame (void) { @@ -1154,8 +1182,8 @@ static bit_t decodeFrame (void) { continue; } case MCMD_PING_SET: { +#if !defined(DISABLE_PING) u4_t freq = convFreq(&opts[oidx+1]); - oidx += 4; u1_t flags = 0x80; if( freq != 0 ) { flags |= MCMD_PING_ANS_FQACK; @@ -1165,9 +1193,12 @@ static bit_t decodeFrame (void) { DO_DEVDB(LMIC.ping.dr, pingDr); } LMIC.pingSetAns = flags; +#endif // !DISABLE_PING + oidx += 4; continue; } case MCMD_BCNI_ANS: { +#if !defined(DISABLE_BEACONS) // Ignore if tracking already enabled if( (LMIC.opmode & OP_TRACK) == 0 ) { LMIC.bcnChnl = opts[oidx+3]; @@ -1191,6 +1222,7 @@ static bit_t decodeFrame (void) { - LMIC.bcnRxtime) << 8)), e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); } +#endif // !DISABLE_BEACONS oidx += 4; continue; } @@ -1290,10 +1322,13 @@ static void setupRx1 (osjobcb_t func) { // 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 / rps (US only) before we increment txChnl setRx1Params(); // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) @@ -1317,6 +1352,7 @@ static void txDone (ostime_t delay, osjobcb_t func) { // ======================================== Join frames +#if !defined(DISABLE_JOIN) static void onJoinFailed (xref2osjob_t osjob) { // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. @@ -1451,6 +1487,8 @@ static void jreqDone (xref2osjob_t osjob) { txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); } +#endif // !DISABLE_JOIN + // ======================================== Data frames // Fwd decl. @@ -1505,12 +1543,14 @@ static void buildDataFrame (void) { // Piggyback MAC options // Prioritize by importance int end = OFF_DAT_OPTS; +#if !defined(DISABLE_PING) 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; } +#endif // !DISABLE_PING if( LMIC.dutyCapAns ) { LMIC.frame[end] = MCMD_DCAP_ANS; end += 1; @@ -1535,21 +1575,25 @@ static void buildDataFrame (void) { end += 2; LMIC.ladrAns = 0; } +#if !defined(DISABLE_BEACONS) if( LMIC.bcninfoTries > 0 ) { LMIC.frame[end] = MCMD_BCNI_REQ; end += 1; } +#endif // !DISABLE_BEACONS if( LMIC.adrChanged ) { if( LMIC.adrAckReq < 0 ) LMIC.adrAckReq = 0; LMIC.adrChanged = 0; } +#if !defined(DISABLE_PING) 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; } +#endif // !DISABLE_PING if( LMIC.snchAns ) { LMIC.frame[end+0] = MCMD_SNCH_ANS; LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU; @@ -1615,6 +1659,7 @@ static void buildDataFrame (void) { } +#if !defined(DISABLE_BEACONS) // 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. @@ -1678,6 +1723,7 @@ void LMIC_disableTracking (void) { LMIC.bcninfoTries = 0; engineUpdate(); } +#endif // !DISABLE_BEACONS @@ -1711,6 +1757,7 @@ void LMIC_disableTracking (void) { // // ================================================================================ +#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. @@ -1757,6 +1804,7 @@ bit_t LMIC_startJoining (void) { } return 0; // already joined } +#endif // !DISABLE_JOIN // ================================================================================ @@ -1765,6 +1813,7 @@ bit_t LMIC_startJoining (void) { // // ================================================================================ +#if !defined(DISABLE_PING) static void processPingRx (xref2osjob_t osjob) { if( LMIC.dataLen != 0 ) { LMIC.txrxFlags = TXRX_PING; @@ -1776,6 +1825,7 @@ static void processPingRx (xref2osjob_t osjob) { // Pick next ping slot engineUpdate(); } +#endif // !DISABLE_PING static bit_t processDnData (void) { @@ -1821,6 +1871,7 @@ static bit_t processDnData (void) { LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; reportEvent(EV_LINK_DEAD); } +#if !defined(DISABLE_BEACONS) // 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 ) { @@ -1831,6 +1882,7 @@ static bit_t processDnData (void) { startScan(); // NWK did not answer - try scan } } +#endif // !DISABLE_BEACONS return 1; } if( !decodeFrame() ) { @@ -1842,6 +1894,7 @@ static bit_t processDnData (void) { } +#if !defined(DISABLE_BEACONS) static void processBeacon (xref2osjob_t osjob) { ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite u1_t flags = LMIC.bcninfo.flags; @@ -1897,8 +1950,10 @@ static void processBeacon (xref2osjob_t osjob) { #if CFG_us915 LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; #endif +#if !defined(DISABLE_PING) if( (LMIC.opmode & OP_PINGINI) != 0 ) rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! +#endif // !DISABLE_PING reportEvent(ev); } @@ -1907,12 +1962,15 @@ static void startRxBcn (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processBeacon); os_radio(RADIO_RX); } +#endif // !DISABLE_BEACONS +#if !defined(DISABLE_PING) static void startRxPing (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processPingRx); os_radio(RADIO_RX); } +#endif // !DISABLE_PING // Decide what to do next for the MAC layer of a device @@ -1921,20 +1979,24 @@ static void engineUpdate (void) { 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; } +#endif // !DISABLE_JOIN ostime_t now = os_getTime(); ostime_t rxtime = 0; ostime_t txbeg = 0; +#if !defined(DISABLE_BEACONS) if( (LMIC.opmode & OP_TRACK) != 0 ) { // We are tracking a beacon ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); rxtime = LMIC.bcnRxtime - RX_RAMPUP; } +#endif // !DISABLE_BEACONS if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { // Need to TX some data... @@ -1950,6 +2012,7 @@ static void engineUpdate (void) { // 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 && @@ -1960,11 +2023,13 @@ static void engineUpdate (void) { 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 ) { @@ -1975,7 +2040,9 @@ static void engineUpdate (void) { } buildJoinRequest(ftype); LMIC.osjob.func = FUNC_ADDR(jreqDone); - } else { + } 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, @@ -2020,8 +2087,10 @@ static void engineUpdate (void) { 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+RX_RAMPUP) ) { @@ -2038,6 +2107,7 @@ static void engineUpdate (void) { } // no - just wait for the beacon } +#endif // !DISABLE_PING if( txbeg != 0 && (txbeg - rxtime) < 0 ) goto txdelay; @@ -2052,6 +2122,7 @@ static void engineUpdate (void) { } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); return; +#endif // !DISABLE_BEACONS txdelay: EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY, @@ -2095,9 +2166,11 @@ void LMIC_reset (void) { LMIC.adrEnabled = FCT_ADREN; LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept LMIC.dn2Freq = FREQ_DNW2; // ditto +#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 #if defined(CFG_us915) initDefaultChannels(); #endif @@ -2105,9 +2178,11 @@ void LMIC_reset (void) { 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 } diff --git a/lmic/lmic.h b/lmic/lmic.h index 75757ad..7b0a5b0 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -89,6 +89,7 @@ enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD }; enum { KEEP_TXPOW = -128 }; +#if !defined(DISABLE_PING) //! \internal struct rxsched_t { u1_t dr; @@ -100,8 +101,10 @@ struct rxsched_t { u4_t freq; }; TYPEDEF_xref2rxsched_t; //!< \internal +#endif // !DISABLE_PING +#if !defined(DISABLE_BEACONS) //! Parsing and tracking states of beacons. enum { BCN_NONE = 0x00, //!< No beacon received BCN_PARTIAL = 0x01, //!< Only first (common) part could be decoded (info,lat,lon invalid/previous) @@ -120,6 +123,7 @@ struct bcninfo_t { s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) }; +#endif // !DISABLE_BEACONS // purpose of receive window - lmic_t.rxState enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 }; @@ -230,10 +234,16 @@ struct lmic_t { u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs // Class B state +#if !defined(DISABLE_BEACONS) u1_t missedBcns; // unable to track last N beacons u1_t bcninfoTries; // how often to try (scan mode only) +#endif +#if !defined(DISABLE_PING) u1_t pingSetAns; // answer set cmd and ACK bits +#endif +#if !defined(DISABLE_PING) rxsched_t ping; // pingable setup +#endif // Public part of MAC state u1_t txCnt; @@ -242,10 +252,12 @@ struct lmic_t { u1_t dataLen; // 0 no data or zero length data, >0 byte count of data u1_t frame[MAX_LEN_FRAME]; +#if !defined(DISABLE_BEACONS) u1_t bcnChnl; u1_t bcnRxsyms; // ostime_t bcnRxtime; bcninfo_t bcninfo; // Last received beacon info +#endif u1_t noRXIQinversion; }; @@ -264,7 +276,9 @@ void LMIC_disableChannel (u1_t channel); void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) +#if !defined(DISABLE_JOIN) bit_t LMIC_startJoining (void); +#endif void LMIC_shutdown (void); void LMIC_init (void); @@ -274,12 +288,18 @@ void LMIC_setTxData (void); int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); void LMIC_sendAlive (void); +#if !defined(DISABLE_BEACONS) bit_t LMIC_enableTracking (u1_t tryBcnInfo); void LMIC_disableTracking (void); +#endif +#if !defined(DISABLE_PING) void LMIC_stopPingable (void); void LMIC_setPingable (u1_t intvExp); +#endif +#if !defined(DISABLE_JOIN) void LMIC_tryRejoin (void); +#endif void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey); void LMIC_setLinkCheckMode (bit_t enabled); From 1063f735f43155ae96b874d024cfb53108115762 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 4 Feb 2016 18:00:29 +0100 Subject: [PATCH 03/27] Allow disabling some MAC commands This causes these commands to be silently ignored, but they are recognized and skipped, so MAC commands coming after them can still be processed. Not all commands can be disabled, since commands like ADR are typically needed to prevent nodes from interfering with proper network operation. --- lmic/lmic.c | 41 +++++++++++++++++++++++++++++++---------- lmic/lmic.h | 8 +++++++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 3dedc9c..de96400 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -938,8 +938,17 @@ 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; -#if !defined(DISABLE_PING) +#if !defined(DISABLE_MCMD_SNCH_REQ) + LMIC.snchAns = 0; +#endif +#if !defined(DISABLE_MCMD_DN2P_SET) + LMIC.dn2Ans = 0; +#endif + LMIC.moreData = 0; +#if !defined(DISABLE_MCMD_DCAP_REQ) + LMIC.dutyCapAns = 0; +#endif +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) LMIC.pingSetAns = 0; #endif LMIC.upRepeat = 0; @@ -1143,9 +1152,9 @@ static bit_t decodeFrame (void) { continue; } case MCMD_DN2P_SET: { +#if !defined(DISABLE_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; @@ -1157,11 +1166,13 @@ static bit_t decodeFrame (void) { DO_DEVDB(LMIC.dn2Dr,dn2Dr); DO_DEVDB(LMIC.dn2Freq,dn2Freq); } +#endif // !DISABLE_MCMD_DN2P_SET + oidx += 5; continue; } case MCMD_DCAP_REQ: { +#if !defined(DISABLE_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 @@ -1169,20 +1180,24 @@ static bit_t decodeFrame (void) { LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); LMIC.dutyCapAns = 1; + oidx += 2; +#endif // !DISABLE_MCMD_DCAP_REQ continue; } case MCMD_SNCH_REQ: { +#if !defined(DISABLE_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; +#endif // !DISABLE_MCMD_SNCH_REQ oidx += 6; continue; } case MCMD_PING_SET: { -#if !defined(DISABLE_PING) +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) u4_t freq = convFreq(&opts[oidx+1]); u1_t flags = 0x80; if( freq != 0 ) { @@ -1193,12 +1208,12 @@ static bit_t decodeFrame (void) { DO_DEVDB(LMIC.ping.dr, pingDr); } LMIC.pingSetAns = flags; -#endif // !DISABLE_PING +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING oidx += 4; continue; } case MCMD_BCNI_ANS: { -#if !defined(DISABLE_BEACONS) +#if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS) // Ignore if tracking already enabled if( (LMIC.opmode & OP_TRACK) == 0 ) { LMIC.bcnChnl = opts[oidx+3]; @@ -1222,7 +1237,7 @@ static bit_t decodeFrame (void) { - LMIC.bcnRxtime) << 8)), e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); } -#endif // !DISABLE_BEACONS +#endif // !DISABLE_MCMD_BCNI_ANS && !DISABLE_BEACONS oidx += 4; continue; } @@ -1551,17 +1566,21 @@ static void buildDataFrame (void) { end += 2; } #endif // !DISABLE_PING +#if !defined(DISABLE_MCMD_DCAP_REQ) if( LMIC.dutyCapAns ) { LMIC.frame[end] = MCMD_DCAP_ANS; end += 1; LMIC.dutyCapAns = 0; } +#endif // !DISABLE_MCMD_DCAP_REQ +#if !defined(DISABLE_MCMD_DN2P_SET) 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; } +#endif // !DISABLE_MCMD_DN2P_SET if( LMIC.devsAns ) { // answer to device status LMIC.frame[end+0] = MCMD_DEVS_ANS; LMIC.frame[end+1] = LMIC.margin; @@ -1586,20 +1605,22 @@ static void buildDataFrame (void) { LMIC.adrAckReq = 0; LMIC.adrChanged = 0; } -#if !defined(DISABLE_PING) +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) 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; } -#endif // !DISABLE_PING +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING +#if !defined(DISABLE_MCMD_SNCH_REQ) 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; } +#endif // !DISABLE_MCMD_SNCH_REQ ASSERT(end <= OFF_DAT_OPTS+16); u1_t flen = end + (txdata ? 5+dlen : 4); diff --git a/lmic/lmic.h b/lmic/lmic.h index 7b0a5b0..e85be82 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -226,19 +226,25 @@ struct lmic_t { bit_t devsAns; // device status answer pending u1_t adrEnabled; u1_t moreData; // NWK has more data pending +#if !defined(DISABLE_MCMD_DCAP_REQ) bit_t dutyCapAns; // have to ACK duty cycle settings +#endif +#if !defined(DISABLE_MCMD_SNCH_REQ) u1_t snchAns; // answer set new channel +#endif // 2nd RX window (after up stream) u1_t dn2Dr; u4_t dn2Freq; +#if !defined(DISABLE_MCMD_DN2P_SET) u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs +#endif // Class B state #if !defined(DISABLE_BEACONS) u1_t missedBcns; // unable to track last N beacons u1_t bcninfoTries; // how often to try (scan mode only) #endif -#if !defined(DISABLE_PING) +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) u1_t pingSetAns; // answer set cmd and ACK bits #endif #if !defined(DISABLE_PING) From ff61a15b456020486e5d5477b43f036b5741a109 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 17 Feb 2016 18:51:04 +0100 Subject: [PATCH 04/27] Fix os_rlsbf4() and os_rmsbf4() when int is smaller than 32 bits These functions convert four individiual bytes from a buffer into a 32-bit integer. The upper two bytes were cast to u4_t before being shifted in place, but not the lower bytes. It seems that this was not needed, since even when int is 16-bits, shifting a byte by 8 will not cause any bits to "fall off". However, a problem with sign-extension would occur in some cases. The problematic expression is: return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); Here, `buf[1]` would be integer-promoted before shifting. It is promoted to a *signed* `int`, since the original `u1_t` type is small enough to fit in there. Now, if the MSB of `buf[1]` is 1, it is then shifted to become the MSB (i.e. sign bit) of the resulting *signed* integer. Then, before the bitwise-or operator is applied, this value is extended to 32-bits, since the right operand is 32-bit. Since the left-hand side is signed, it is sign-extended, causing all upper 16 bits to become 1, making them also 1 in the resulting value. To fix this, all bytes are first explicitely cast to `u4_t` to make sure they are already big enough and remain unsigned. For consistency, the same is done for `os_rlsbf2()`, even though this same problem cannot occur there (C guarantees that an int is at least 16-bits wide). --- lmic/lmic.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index de96400..4b18098 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -77,20 +77,20 @@ static void startScan (void); #if !defined(os_rlsbf2) u2_t os_rlsbf2 (xref2cu1_t buf) { - return (u2_t)(buf[0] | (buf[1]<<8)); + 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)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); + 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)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); + return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); } #endif From d4feb12bbd3ad0f279aacc7d4879ffc0f4e65690 Mon Sep 17 00:00:00 2001 From: Oliv Date: Wed, 13 Apr 2016 22:49:27 +0200 Subject: [PATCH 05/27] Correction of DevStatusAns Battery level and margin was exchanged, see LoraWAN specification chapter 5.5 --- lmic/lmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 4b18098..3ddb42c 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1583,8 +1583,8 @@ static void buildDataFrame (void) { #endif // !DISABLE_MCMD_DN2P_SET 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(); + LMIC.frame[end+1] = os_getBattLevel(); + LMIC.frame[end+2] = LMIC.margin; end += 3; LMIC.devsAns = 0; } From 9ecb8de3d15dd7a777be0b3188de0df5b83a0c52 Mon Sep 17 00:00:00 2001 From: Frank Leon Rose Date: Thu, 21 Jul 2016 22:52:50 -0400 Subject: [PATCH 06/27] Added functions to enable/disable/select sub-bands --- lmic/lmic.c | 29 +++++++++++++++++++++++++++++ lmic/lmic.h | 6 ++++++ 2 files changed, 35 insertions(+) diff --git a/lmic/lmic.c b/lmic/lmic.c index 3ddb42c..4914f75 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -783,6 +783,35 @@ void LMIC_disableChannel (u1_t channel) { LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF)); } +void LMIC_enableChannel (u1_t channel) { + if( channel < 72+MAX_XCHANNELS ) + LMIC.channelMap[channel>>4] |= (1<<(channel&0xF)); +} + +void LMIC_enableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_enableChannel(channel); +} +void LMIC_disableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_disableChannel(channel); +} +void LMIC_selectSubBand (u1_t band) { + ASSERT(band < 8); + for (int b=0; b<8; ++b) { + if (band==b) + LMIC_enableSubBand(b); + else + LMIC_disableSubBand(b); + } +} + 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; diff --git a/lmic/lmic.h b/lmic/lmic.h index e85be82..d22b166 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -279,6 +279,12 @@ bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap); #endif bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band); void LMIC_disableChannel (u1_t channel); +#if defined(CFG_us915) +void LMIC_enableChannel (u1_t channel); +void LMIC_enableSubBand (u1_t band); +void LMIC_disableSubBand (u1_t band); +void LMIC_selectSubBand (u1_t band); +#endif void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) From 98259c40c82d79a6d0bdbb7b37d379fa2d827ac2 Mon Sep 17 00:00:00 2001 From: Frank Leon Rose Date: Sun, 26 Jun 2016 10:10:44 -0400 Subject: [PATCH 07/27] Fix LMIC_disableChannel for CFG_us915 --- lmic/lmic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 4914f75..afe9560 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -780,7 +780,7 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { void LMIC_disableChannel (u1_t channel) { if( channel < 72+MAX_XCHANNELS ) - LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF)); + LMIC.channelMap[channel>>4] &= ~(1<<(channel&0xF)); } void LMIC_enableChannel (u1_t channel) { From de8d8d1c144e7800d2dd388af046e37c8cc87a5e Mon Sep 17 00:00:00 2001 From: Oliv4945 Date: Thu, 23 Jun 2016 23:29:11 +0200 Subject: [PATCH 08/27] Add `rxDelay`variable to prepare joinAccept parsing --- lmic/lmic.c | 7 +++---- lmic/lmic.h | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index afe9560..2b30630 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -43,8 +43,6 @@ #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) @@ -1564,7 +1562,7 @@ static void setupRx2DnData (xref2osjob_t osjob) { static void processRx1DnData (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processDnData() ) - schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); + schedRx2(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData)); } @@ -1574,7 +1572,7 @@ static void setupRx1DnData (xref2osjob_t osjob) { static void updataDone (xref2osjob_t osjob) { - txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData)); + txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); } // ======================================== @@ -2216,6 +2214,7 @@ void LMIC_reset (void) { LMIC.adrEnabled = FCT_ADREN; LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept LMIC.dn2Freq = FREQ_DNW2; // ditto + LMIC.rxDelay = DELAY_DNW1; #if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; // defaults for ping LMIC.ping.dr = DR_PING; // ditto diff --git a/lmic/lmic.h b/lmic/lmic.h index d22b166..344a410 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -221,6 +221,8 @@ struct lmic_t { s1_t adrAckReq; // counter until we reset data rate (0=off) u1_t adrChanged; + u1_t rxDelay; // Rx delay after TX + u1_t margin; bit_t ladrAns; // link adr adapt answer pending bit_t devsAns; // device status answer pending From bc42a778b154643c02c27c994a23c764a574ca20 Mon Sep 17 00:00:00 2001 From: Oliv4945 Date: Thu, 23 Jun 2016 23:32:55 +0200 Subject: [PATCH 09/27] Parse `rxDelay` from joinAccept The LoraWAN spec 1R0 specifies that a value of 0 is one second --- lmic/lmic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lmic/lmic.c b/lmic/lmic.c index 2b30630..d636bea 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1496,6 +1496,8 @@ static bit_t processJoinAccept (void) { } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; stateJustJoined(); + LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; + if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; reportEvent(EV_JOINED); return 1; } From c6d1e3bc7507bebdd32b9de5a05f6a2b1c14520b Mon Sep 17 00:00:00 2001 From: Oliv4945 Date: Thu, 23 Jun 2016 23:33:46 +0200 Subject: [PATCH 10/27] Process `dn2Dr` from joinAccept --- lmic/lmic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lmic/lmic.c b/lmic/lmic.c index d636bea..8893754 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1496,6 +1496,7 @@ static bit_t processJoinAccept (void) { } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; stateJustJoined(); + LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; reportEvent(EV_JOINED); From bdc511e6b76212edd07dc3c49bf505ae09eb005a Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jun 2016 19:45:27 +0200 Subject: [PATCH 11/27] Reuse schedRx2() for RX1 as well This reduces some code duplication, and prepares for an upcoming change that makes the scheduling a bit more complex. To reflect the more generic usage of this function, it is renamed to schedRx12. --- lmic/lmic.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 8893754..84a90f7 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1346,9 +1346,9 @@ static void setupRx2 (void) { } -static void schedRx2 (ostime_t delay, osjobcb_t func) { +static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { // 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); + LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(dr); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -1380,14 +1380,14 @@ static void txDone (ostime_t delay, osjobcb_t func) { if( /* TX datarate */LMIC.rxsyms == DR_FSK ) { LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); LMIC.rxsyms = RXLEN_FSK; + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } else #endif { - LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dndr); + schedRx12(delay, func, LMIC.dndr); LMIC.rxsyms = MINRX_SYMS; } - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -1519,7 +1519,7 @@ static void setupRx2Jacc (xref2osjob_t osjob) { static void processRx1Jacc (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processJoinAccept() ) - schedRx2(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc)); + schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); } @@ -1565,7 +1565,7 @@ static void setupRx2DnData (xref2osjob_t osjob) { static void processRx1DnData (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processDnData() ) - schedRx2(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData)); + schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); } From 37dcc5b47859f38bb15d76b9a2beeb3048bded16 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 9 Jun 2016 19:51:16 +0200 Subject: [PATCH 12/27] Remove some unused fields from lmic_t when DISABLE_BEACONS is defined --- lmic/lmic.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lmic/lmic.h b/lmic/lmic.h index 344a410..ef42b7f 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -201,9 +201,11 @@ struct lmic_t { u1_t datarate; // current data rate u1_t errcr; // error coding rate (used for TX only) u1_t rejoinCnt; // adjustment for rejoin datarate +#if !defined(DISABLE_BEACONS) s2_t drift; // last measured drift s2_t lastDriftDiff; s2_t maxDriftDiff; +#endif u1_t pendTxPort; u1_t pendTxConf; // confirmed data From 8a5f61c172e1b27fc6f89357807270de69384cae Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 10 Jun 2016 13:30:06 +0200 Subject: [PATCH 13/27] Allow compensating for clock inaccuracy by increasing the RX windows When using beacons and ping slots, there is already support for measuring the actual clock drift and compensating for it. However, class A nodes have no beacons to measure their drift, so this commit allows them to configure the maximum clock error (e.g. +/- 1%) and increases the receive windows to ensure that even with the worst-case clock values, messages will be received. Note that because the receive window is measures in symbols, with a high drift rate and/or high datarate the resulting receive window might be smaller than it should be. This could be slightly improved by using all 10 bits available in the hardware register (currently only 8 bits are used). Also note that this moves the calculation of rxsyms into schedRx12, since the receive window length can be different between RX1 and RX2 (previously it was set only for RX1 and kept around). As a side effect, this probably breaks FSK reception, since txDone checks rxsyms to see if it is DR_FSK and sets the length differently. With the current code, schedRx12 will override that rxsyms value for RX2. However, I could not find where rxsyms is ever set to DR_FSK (and it does not seem sane to use that variable like that), so I wonder if FSK reception will work at all. --- lmic/lmic.c | 39 +++++++++++++++++++++++++++++++++++---- lmic/lmic.h | 8 ++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 84a90f7..5da8b8b 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1347,8 +1347,35 @@ static void setupRx2 (void) { static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { - // 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(dr); + ostime_t hsym = dr2hsym(dr); + + LMIC.rxsyms = MINRX_SYMS; + + // If a clock error is specified, compensate for it by extending the + // receive window + if (LMIC.clockError != 0) { + // Calculate how much the clock will drift maximally after delay has + // passed. This indicates the amount of time we can be early + // _or_ late. + ostime_t drift = (s8_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; + + // Increase the receive window by twice the maximum drift (to + // compensate for a slow or a fast clock). + // decrease the rxtime to compensate for. Note that hsym is a + // *half* symbol time, so the factor 2 is hidden. First check if + // this would overflow (which can happen if the drift is very + // high, or the symbol time is low at high datarates). + if ((255 - LMIC.rxsyms) * hsym < drift) + LMIC.rxsyms = 255; + else + LMIC.rxsyms += drift / hsym; + + } + + // Center the receive window on the center of the expected preamble + // (again note that hsym is half a sumbol time, so no /2 needed) + LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -1386,7 +1413,6 @@ static void txDone (ostime_t delay, osjobcb_t func) { #endif { schedRx12(delay, func, LMIC.dndr); - LMIC.rxsyms = MINRX_SYMS; } } @@ -2339,4 +2365,9 @@ void LMIC_setLinkCheckMode (bit_t enabled) { 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.clockError = error; +} diff --git a/lmic/lmic.h b/lmic/lmic.h index ef42b7f..8ad61f0 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -163,6 +163,10 @@ enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, EV_TXSTART }; typedef enum _ev_t ev_t; +enum { + // This value represents 100% error in LMIC.clockError + MAX_CLOCK_ERROR = 65536, +}; struct lmic_t { // Radio settings TX/RX (also accessed by HAL) @@ -207,6 +211,9 @@ struct lmic_t { s2_t maxDriftDiff; #endif + u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX + // represents +/-100% error + u1_t pendTxPort; u1_t pendTxConf; // confirmed data u1_t pendTxLen; // +0x80 = confirmed @@ -319,6 +326,7 @@ void LMIC_tryRejoin (void); void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey); void LMIC_setLinkCheckMode (bit_t enabled); +void LMIC_setClockError(u2_t error); // Declare onEvent() function, to make sure any definition will have the // C conventions, even when in a C++ file. From 8ec6045eff8b2859cbb9aab61908eeed2adfee73 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 10 Jun 2016 16:10:31 +0200 Subject: [PATCH 14/27] Add alternative AES implementation The original LMIC AES implementation is optimized for fast execution on 32bit processors, but it ends up taking a lot of flash space on 8-bit AVR processors. This commit adds an alternative AES implementation written by Ideetron, which is a lot smaller, but also about twice as slow as the original imlementation. This new implementation is selected by default, but the original can be used by modifying config.h. --- aes/ideetron/AES-128_V10.cpp | 341 +++++++++++++++++++++++++++++++++++ lmic/aes.c => aes/lmic.c | 3 + aes/other.c | 145 +++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 aes/ideetron/AES-128_V10.cpp rename lmic/aes.c => aes/lmic.c (99%) create mode 100644 aes/other.c diff --git a/aes/ideetron/AES-128_V10.cpp b/aes/ideetron/AES-128_V10.cpp new file mode 100644 index 0000000..5d8533c --- /dev/null +++ b/aes/ideetron/AES-128_V10.cpp @@ -0,0 +1,341 @@ +/****************************************************************************************** +#if defined(USE_IDEETRON_AES) +* Copyright 2015, 2016 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/****************************************************************************************** +* +* File: AES-128_V10.cpp +* Author: Gerben den Hartog +* Compagny: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +******************************************************************************************/ +/**************************************************************************************** +* +* Created on: 20-10-2015 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +* +* Firmware Version 1.0 +* First version +****************************************************************************************/ + +// This file was taken from +// https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for +// use with LMIC. It was only cosmetically modified: +// - AES_Encrypt was renamed to aes_encrypt. +// - All other functions and variables were made static +// - Tabs were converted to 2 spaces +// - An #include and #if guard was added + +#include "../../lmic/config.h" + +#if defined(USE_IDEETRON_AES) + +/* +******************************************************************************************** +* Global Variables +******************************************************************************************** +*/ + +static unsigned char State[4][4]; + +static unsigned char S_Table[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +extern "C" void aes_encrypt(unsigned char *Data, unsigned char *Key); +static void AES_Add_Round_Key(unsigned char *Round_Key); +static unsigned char AES_Sub_Byte(unsigned char Byte); +static void AES_Shift_Rows(); +static void AES_Mix_Collums(); +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); +static void Send_State(); + +/* +***************************************************************************************** +* Description : Function for encrypting data using AES-128 +* +* Arguments : *Data Data to encrypt is a 16 byte long arry +* *Key Key to encrypt data with is a 16 byte long arry +***************************************************************************************** +*/ +void aes_encrypt(unsigned char *Data, unsigned char *Key) +{ + unsigned char i; + unsigned char Row,Collum; + unsigned char Round = 0x00; + unsigned char Round_Key[16]; + + //Copy input to State arry + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = Data[Row + (4*Collum)]; + } + } + + //Copy key to round key + for(i = 0; i < 16; i++) + { + Round_Key[i] = Key[i]; + } + + //Add round key + AES_Add_Round_Key(Round_Key); + + //Preform 9 full rounds + for(Round = 1; Round < 10; Round++) + { + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Preform Row Shift + AES_Shift_Rows(); + + //Mix Collums + AES_Mix_Collums(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round key + AES_Add_Round_Key(Round_Key); + } + + //Last round whitout mix collums + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Shift rows + AES_Shift_Rows(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round Key + AES_Add_Round_Key(Round_Key); + + //Copy the State into the data array + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + Data[Row + (4*Collum)] = State[Row][Collum]; + } + } + +} + +/* +***************************************************************************************** +* Description : Function that add's the round key for the current round +* +* Arguments : *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Add_Round_Key(unsigned char *Round_Key) +{ + unsigned char Row,Collum; + + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = State[Row][Collum] ^ Round_Key[Row + (4*Collum)]; + } + } +} + +/* +***************************************************************************************** +* Description : Function that substitutes a byte with a byte from the S_Table +* +* Arguments : Byte The byte that will be substituted +* +* Return : The return is the found byte in the S_Table +***************************************************************************************** +*/ +static unsigned char AES_Sub_Byte(unsigned char Byte) +{ + unsigned char S_Row,S_Collum; + unsigned char S_Byte; + + //Split byte up in Row and Collum + S_Row = ((Byte >> 4) & 0x0F); + S_Collum = (Byte & 0x0F); + + //Find the correct byte in the S_Table + S_Byte = S_Table[S_Row][S_Collum]; + + return S_Byte; +} + +/* +***************************************************************************************** +* Description : Function that preforms the shift row operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Shift_Rows() +{ + unsigned char Buffer; + + //Row 0 doesn't change + + //Shift Row 1 one left + //Store firt byte in buffer + Buffer = State[1][0]; + //Shift all bytes + State[1][0] = State[1][1]; + State[1][1] = State[1][2]; + State[1][2] = State[1][3]; + State[1][3] = Buffer; + + //Shift row 2 two left + Buffer = State[2][0]; + State[2][0] = State[2][2]; + State[2][2] = Buffer; + Buffer = State[2][1]; + State[2][1] = State[2][3]; + State[2][3] = Buffer; + + //Shift row 3 three left + Buffer = State[3][3]; + State[3][3] = State[3][2]; + State[3][2] = State[3][1]; + State[3][1] = State[3][0]; + State[3][0] = Buffer; +} + +/* +***************************************************************************************** +* Description : Function that preforms the Mix Collums operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Mix_Collums() +{ + unsigned char Row,Collum; + unsigned char a[4], b[4]; + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + a[Row] = State[Row][Collum]; + b[Row] = (State[Row][Collum] << 1); + + if((State[Row][Collum] & 0x80) == 0x80) + { + b[Row] = b[Row] ^ 0x1B; + } + } + State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; + State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; + State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; + State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; + } +} + +/* +***************************************************************************************** +* Description : Function that calculaties the round key for the current round +* +* Arguments : Round Number of current Round +* *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) +{ + unsigned char i,j; + unsigned char b; + unsigned char Temp[4]; + unsigned char Buffer; + unsigned char Rcon; + + //Calculate first Temp + //Copy laste byte from previous key + for(i = 0; i < 4; i++) + { + Temp[i] = Round_Key[i+12]; + } + + //Rotate Temp + Buffer = Temp[0]; + Temp[0] = Temp[1]; + Temp[1] = Temp[2]; + Temp[2] = Temp[3]; + Temp[3] = Buffer; + + //Substitute Temp + for(i = 0; i < 4; i++) + { + Temp[i] = AES_Sub_Byte(Temp[i]); + } + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + if(b == 0x80) + { + Rcon = Rcon ^ 0x1b; + } + Round--; + } + + //XOR Rcon + Temp[0] = Temp[0] ^ Rcon; + + //Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (4*i)] = Round_Key[j + (4*i)] ^ Temp[j]; + Temp[j] = Round_Key[j + (4*i)]; + } + } +} + +#endif // defined(USE_IDEETRON_AES) diff --git a/lmic/aes.c b/aes/lmic.c similarity index 99% rename from lmic/aes.c rename to aes/lmic.c index d2009e9..1f5a313 100644 --- a/lmic/aes.c +++ b/aes/lmic.c @@ -25,6 +25,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if defined(USE_ORIGINAL_AES) + #include "oslmic.h" #define AES_MICSUB 0x30 // internal use only @@ -381,3 +383,4 @@ u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) { return AESAUX[0]; } +#endif diff --git a/aes/other.c b/aes/other.c new file mode 100644 index 0000000..ceb5089 --- /dev/null +++ b/aes/other.c @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2016 Matthijs Kooijman + * + * LICENSE + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and + * redistribution. + * + * NO WARRANTY OF ANY KIND IS PROVIDED. + *******************************************************************************/ + +/* + * The original LMIC AES implementation integrates raw AES encryption + * with CMAC and AES-CTR in a single piece of code. Most other AES + * implementations (only) offer raw single block AES encryption, so this + * file contains an implementation of CMAC and AES-CTR, and offers the + * same API through the os_aes() function as the original AES + * implementation. This file assumes that there is an encryption + * function available with this signature: + * + * extern "C" void aes_encrypt(u1_t *data, u1_t *key); + * + * That takes a single 16-byte buffer and encrypts it wit the given + * 16-byte key. + */ + +#include "../lmic/oslmic.h" + +#if !defined(USE_ORIGINAL_AES) + +// This should be defined elsewhere +void aes_encrypt(u1_t *data, u1_t *key); + +// global area for passing parameters (aux, key) and for storing round keys +u4_t AESAUX[16/sizeof(u4_t)]; +u4_t AESKEY[11*16/sizeof(u4_t)]; + +// Shift the given buffer left one bit +static void shift_left(xref2u1_t buf, u1_t len) { + while (len--) { + u1_t next = len ? buf[1] : 0; + + u1_t val = (*buf << 1); + if (next & 0x80) + val |= 1; + *buf++ = val; + } +} + +// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true, +// AESAUX is prepended to the message. AESAUX is used as working memory +// in any case. The CMAC result is returned in AESAUX as well. +static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) { + if (prepend_aux) + aes_encrypt(AESaux, AESkey); + else + memset (AESaux, 0, 16); + + while (len > 0) { + u1_t need_padding = 0; + for (u1_t i = 0; i < 16; ++i, ++buf, --len) { + if (len == 0) { + // The message is padded with 0x80 and then zeroes. + // Since zeroes are no-op for xor, we can just skip them + // and leave AESAUX unchanged for them. + AESaux[i] ^= 0x80; + need_padding = 1; + break; + } + AESaux[i] ^= *buf; + } + + if (len == 0) { + // Final block, xor with K1 or K2. K1 and K2 are calculated + // by encrypting the all-zeroes block and then applying some + // shifts and xor on that. + u1_t final_key[16]; + memset(final_key, 0, sizeof(final_key)); + aes_encrypt(final_key, AESkey); + + // Calculate K1 + u1_t msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + + // If the final block was not complete, calculate K2 from K1 + if (need_padding) { + msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + } + + // Xor with K1 or K2 + for (u1_t i = 0; i < sizeof(final_key); ++i) + AESaux[i] ^= final_key[i]; + } + + aes_encrypt(AESaux, AESkey); + } +} + +// Run AES-CTR using the key in AESKEY and using AESAUX as the +// counter block. The last byte of the counter block will be incremented +// for every block. The given buffer will be encrypted in place. +static void os_aes_ctr (xref2u1_t buf, u2_t len) { + u1_t ctr[16]; + while (len) { + // Encrypt the counter block with the selected key + memcpy(ctr, AESaux, sizeof(ctr)); + aes_encrypt(ctr, AESkey); + + // Xor the payload with the resulting ciphertext + for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++) + *buf ^= ctr[i]; + + // Increment the block index byte + AESaux[15]++; + } +} + +u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) { + switch (mode & ~AES_MICNOAUX) { + case AES_MIC: + os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX)); + return os_rmsbf4(AESaux); + + case AES_ENC: + // TODO: Check / handle when len is not a multiple of 16 + for (u1_t i = 0; i < len; i += 16) + aes_encrypt(buf+i, AESkey); + break; + + case AES_CTR: + os_aes_ctr(buf, len); + break; + } + return 0; +} + +#endif // !defined(USE_ORIGINAL_AES) From f08f92143133267899d8b43ce5ef7626af2dea51 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Sat, 11 Jun 2016 02:21:55 +0200 Subject: [PATCH 15/27] Put S_Table from the Ideetron AES code in PROGMEM This uses the macros previously created for the original AES code to put these values in PROGMEM in a portable manner. --- aes/ideetron/AES-128_V10.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aes/ideetron/AES-128_V10.cpp b/aes/ideetron/AES-128_V10.cpp index 5d8533c..909fe93 100644 --- a/aes/ideetron/AES-128_V10.cpp +++ b/aes/ideetron/AES-128_V10.cpp @@ -39,8 +39,9 @@ // - All other functions and variables were made static // - Tabs were converted to 2 spaces // - An #include and #if guard was added +// - S_Table is now stored in PROGMEM -#include "../../lmic/config.h" +#include "../../lmic/oslmic.h" #if defined(USE_IDEETRON_AES) @@ -52,7 +53,7 @@ static unsigned char State[4][4]; -static unsigned char S_Table[16][16] = { +static CONST_TABLE(unsigned char, S_Table)[16][16] = { {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, @@ -206,7 +207,7 @@ static unsigned char AES_Sub_Byte(unsigned char Byte) S_Collum = (Byte & 0x0F); //Find the correct byte in the S_Table - S_Byte = S_Table[S_Row][S_Collum]; + S_Byte = TABLE_GET_U1_TWODIM(S_Table, S_Row, S_Collum); return S_Byte; } From 31e746d2176afda6a1e7332f030711a105f09d42 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 8 Aug 2016 11:29:42 +0200 Subject: [PATCH 16/27] Only use valid channels during joining In 03ec06b (Reduce the default channel list), the list of join channels was reduced. However, in `initJoinLoop()`, a random channel was selected for the first join attempt, using a hardcoded amount of join channels, which still had the old value. This means that half of the join attempts would use an invalid channel, configuring a frequency of 0Mhz into the radio, preventing the join request from being received. Each subsequent join uses a next channel, so after at most three join requests valid channels are used again. Eventually a join would complete, but this bug might cause extra delays in joining. --- lmic/lmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 5da8b8b..b32df7f 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -699,7 +699,7 @@ static void initJoinLoop (void) { #if CFG_TxContinuousMode LMIC.txChnl = 0; #else - LMIC.txChnl = os_getRndU1() % 6; + LMIC.txChnl = os_getRndU1() % 3; #endif LMIC.adrTxPow = 14; setDrJoin(DRCHG_SET, DR_SF7); @@ -714,7 +714,7 @@ static ostime_t nextJoinState (void) { // Try 869.x and then 864.x with same DR // If both fail try next lower datarate - if( ++LMIC.txChnl == 6 ) + if( ++LMIC.txChnl == 3 ) LMIC.txChnl = 0; if( (++LMIC.txCnt & 1) == 0 ) { // Lower DR every 2nd try (having tried 868.x and 864.x with the same DR) From d02ccd88055123276803b5265df52829ce93f1c3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 8 Aug 2016 11:54:18 +0200 Subject: [PATCH 17/27] Reset LMIC.txCnt after joining During joining, LMIC.txCnt is used to keep track of retransmissions. Normally, it is cleared in `LMIC_startJoining()` or `LMIC_setTxData()` so it is 0 for each new packet. However, when an automatic joining attempt is started when data is queued, txCnt would not be reset between the join and the actual datapacket. If more than one join attempt was needed, txCnt is non-zero and the data packet will be retried multiple times. Since LMIC.pendTxConf is not set, the packets will not request an ack, so the full number of retries is always used. This is fixed by clearing txCnt after the join completes. --- lmic/lmic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lmic/lmic.c b/lmic/lmic.c index b32df7f..eff28f9 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1521,6 +1521,7 @@ static bit_t processJoinAccept (void) { LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; + LMIC.txCnt = 0; stateJustJoined(); LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; From fbf599d4ae743fecbcebf1cf981c579110f8bf18 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 8 Aug 2016 12:06:40 +0200 Subject: [PATCH 18/27] Do not clear LMIC.pendTxConf in LMIC_startJoining() `pendTxConf` is used to track whether the current TX data packet should request confirmation. It is set by `LMIC_setTxData2()` and only used by `buildDataFrame()`, so it plays no role during a join. When an automatic join was started after queueing a confirmed uplink, clearing `pendTxConf` causes the uplink to be send unconfirmed after the join is complete, which does not seem like the intended behaviour. Simply not touching `pendTxConf` in `LMIC_startJoining()` should preserve the confirmed status properly throughout the join procedure. --- lmic/lmic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index eff28f9..4c1950f 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1873,7 +1873,7 @@ bit_t LMIC_startJoining (void) { // Cancel scanning LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); // Setup state - LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0; + LMIC.rejoinCnt = LMIC.txCnt = 0; initJoinLoop(); LMIC.opmode |= OP_JOINING; // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS From 1be8862e9c4d69442f507dd6d783e28729e6a94f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 9 Aug 2016 22:56:00 +0200 Subject: [PATCH 19/27] Fix compilation with USE_ORIGINAL_AES lmic.c was using a define before including the file that defined it. --- aes/lmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aes/lmic.c b/aes/lmic.c index 1f5a313..2a5bca3 100644 --- a/aes/lmic.c +++ b/aes/lmic.c @@ -25,9 +25,9 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#if defined(USE_ORIGINAL_AES) +#include "../lmic/oslmic.h" -#include "oslmic.h" +#if defined(USE_ORIGINAL_AES) #define AES_MICSUB 0x30 // internal use only From 2c6305b4b03be06f07b392ec7b44462697125ea2 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 9 Aug 2016 22:59:44 +0200 Subject: [PATCH 20/27] Rename aes_encrypt to lmic_aes_encrypt Apparently the ESP core already has a function named aes_encrypt, so this helps to compile this library on that platform. --- aes/ideetron/AES-128_V10.cpp | 6 +++--- aes/other.c | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aes/ideetron/AES-128_V10.cpp b/aes/ideetron/AES-128_V10.cpp index 909fe93..1d790f9 100644 --- a/aes/ideetron/AES-128_V10.cpp +++ b/aes/ideetron/AES-128_V10.cpp @@ -35,7 +35,7 @@ // This file was taken from // https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for // use with LMIC. It was only cosmetically modified: -// - AES_Encrypt was renamed to aes_encrypt. +// - AES_Encrypt was renamed to lmic_aes_encrypt. // - All other functions and variables were made static // - Tabs were converted to 2 spaces // - An #include and #if guard was added @@ -72,7 +72,7 @@ static CONST_TABLE(unsigned char, S_Table)[16][16] = { {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} }; -extern "C" void aes_encrypt(unsigned char *Data, unsigned char *Key); +extern "C" void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key); static void AES_Add_Round_Key(unsigned char *Round_Key); static unsigned char AES_Sub_Byte(unsigned char Byte); static void AES_Shift_Rows(); @@ -88,7 +88,7 @@ static void Send_State(); * *Key Key to encrypt data with is a 16 byte long arry ***************************************************************************************** */ -void aes_encrypt(unsigned char *Data, unsigned char *Key) +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key) { unsigned char i; unsigned char Row,Collum; diff --git a/aes/other.c b/aes/other.c index ceb5089..52febdb 100644 --- a/aes/other.c +++ b/aes/other.c @@ -21,7 +21,7 @@ * implementation. This file assumes that there is an encryption * function available with this signature: * - * extern "C" void aes_encrypt(u1_t *data, u1_t *key); + * extern "C" void lmic_aes_encrypt(u1_t *data, u1_t *key); * * That takes a single 16-byte buffer and encrypts it wit the given * 16-byte key. @@ -32,7 +32,7 @@ #if !defined(USE_ORIGINAL_AES) // This should be defined elsewhere -void aes_encrypt(u1_t *data, u1_t *key); +void lmic_aes_encrypt(u1_t *data, u1_t *key); // global area for passing parameters (aux, key) and for storing round keys u4_t AESAUX[16/sizeof(u4_t)]; @@ -55,7 +55,7 @@ static void shift_left(xref2u1_t buf, u1_t len) { // in any case. The CMAC result is returned in AESAUX as well. static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) { if (prepend_aux) - aes_encrypt(AESaux, AESkey); + lmic_aes_encrypt(AESaux, AESkey); else memset (AESaux, 0, 16); @@ -79,7 +79,7 @@ static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) { // shifts and xor on that. u1_t final_key[16]; memset(final_key, 0, sizeof(final_key)); - aes_encrypt(final_key, AESkey); + lmic_aes_encrypt(final_key, AESkey); // Calculate K1 u1_t msb = final_key[0] & 0x80; @@ -100,7 +100,7 @@ static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) { AESaux[i] ^= final_key[i]; } - aes_encrypt(AESaux, AESkey); + lmic_aes_encrypt(AESaux, AESkey); } } @@ -112,7 +112,7 @@ static void os_aes_ctr (xref2u1_t buf, u2_t len) { while (len) { // Encrypt the counter block with the selected key memcpy(ctr, AESaux, sizeof(ctr)); - aes_encrypt(ctr, AESkey); + lmic_aes_encrypt(ctr, AESkey); // Xor the payload with the resulting ciphertext for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++) @@ -132,7 +132,7 @@ u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) { case AES_ENC: // TODO: Check / handle when len is not a multiple of 16 for (u1_t i = 0; i < len; i += 16) - aes_encrypt(buf+i, AESkey); + lmic_aes_encrypt(buf+i, AESkey); break; case AES_CTR: From c57405ccce537548e64403b789af8458d77e3a2c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 9 Aug 2016 23:03:46 +0200 Subject: [PATCH 21/27] Remove u8_t and s8_t typedefs The ESP core uses these same typedefs, but for 8-bit values instead of 8-byte values, so this prevents compilation on the ESP core. Ideally, all of these custom LMIC typedefs should be replaced by the standard types, but this is a change better made upstream. For now, only u8_t and s8_t are replaced by their standard equivalent, to make things work on ESP. --- lmic/lmic.c | 2 +- lmic/oslmic.h | 20 +++++++++----------- lmic/radio.c | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 4c1950f..bb03d04 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1357,7 +1357,7 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { // Calculate how much the clock will drift maximally after delay has // passed. This indicates the amount of time we can be early // _or_ late. - ostime_t drift = (s8_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; + ostime_t drift = (int64_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; // Increase the receive window by twice the maximum drift (to // compensate for a slow or a fast clock). diff --git a/lmic/oslmic.h b/lmic/oslmic.h index 1cd9e57..698654a 100644 --- a/lmic/oslmic.h +++ b/lmic/oslmic.h @@ -50,8 +50,6 @@ typedef uint16_t u2_t; typedef int16_t s2_t; typedef uint32_t u4_t; typedef int32_t s4_t; -typedef uint64_t u8_t; -typedef int64_t s8_t; typedef unsigned int uint; typedef const char* str_t; @@ -121,16 +119,16 @@ void os_runloop_once (void); typedef s4_t ostime_t; #if !HAS_ostick_conv -#define us2osticks(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000)) -#define ms2osticks(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC) / 1000)) -#define sec2osticks(sec) ((ostime_t)( (s8_t)(sec) * OSTICKS_PER_SEC)) -#define osticks2ms(os) ((s4_t)(((os)*(s8_t)1000 ) / OSTICKS_PER_SEC)) -#define osticks2us(os) ((s4_t)(((os)*(s8_t)1000000 ) / OSTICKS_PER_SEC)) +#define us2osticks(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC) / 1000000)) +#define ms2osticks(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC) / 1000)) +#define sec2osticks(sec) ((ostime_t)( (int64_t)(sec) * OSTICKS_PER_SEC)) +#define osticks2ms(os) ((s4_t)(((os)*(int64_t)1000 ) / OSTICKS_PER_SEC)) +#define osticks2us(os) ((s4_t)(((os)*(int64_t)1000000 ) / OSTICKS_PER_SEC)) // Special versions -#define us2osticksCeil(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) -#define us2osticksRound(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) -#define ms2osticksCeil(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) -#define ms2osticksRound(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) +#define us2osticksCeil(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) +#define us2osticksRound(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) +#define ms2osticksCeil(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) +#define ms2osticksRound(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) #endif diff --git a/lmic/radio.c b/lmic/radio.c index 8ef7d37..043bda4 100644 --- a/lmic/radio.c +++ b/lmic/radio.c @@ -411,7 +411,7 @@ static void configLoraModem () { static void configChannel () { // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19) - u8_t frf = ((u8_t)LMIC.freq << 19) / 32000000; + uint64_t frf = ((uint64_t)LMIC.freq << 19) / 32000000; writeReg(RegFrfMsb, (u1_t)(frf>>16)); writeReg(RegFrfMid, (u1_t)(frf>> 8)); writeReg(RegFrfLsb, (u1_t)(frf>> 0)); From af6beefbb07ae6ea902169a4fd51a48ea08cf511 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 8 Aug 2016 12:16:41 +0200 Subject: [PATCH 22/27] Add some basic debug output --- lmic/lmic.c | 16 +++++++++++++++- lmic/radio.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index bb03d04..d520dc9 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1040,6 +1040,7 @@ static bit_t decodeFrame (void) { u1_t hdr = d[0]; u1_t ftype = hdr & HDR_FTYPE; int dlen = LMIC.dataLen; + const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); if( dlen < OFF_DAT_OPTS+4 || (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { @@ -1049,6 +1050,9 @@ static bit_t decodeFrame (void) { e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]), e_.info2 = hdr + (dlen<<8))); norx: +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: Invalid downlink, window=%s\n", os_getTime(), window); +#endif LMIC.dataLen = 0; return 0; } @@ -1329,6 +1333,9 @@ static bit_t decodeFrame (void) { LMIC.dataBeg = poff; LMIC.dataLen = pend-poff; } +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: Received downlink, window=%s, port=%d, ack=%d\n", os_getTime(), window, port, ackup); +#endif return 1; } @@ -1493,8 +1500,12 @@ static bit_t processJoinAccept (void) { dlen = OFF_CFLIST; for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { u4_t freq = convFreq(&LMIC.frame[dlen]); - if( freq ) + if( freq ) { LMIC_setupChannel(chidx, freq, 0, -1); +#if LMIC_DEBUG_LEVEL > 1 + printf("%lu: Setup channel, idx=%d, freq=%lu\n", os_getTime(), chidx, (unsigned long)freq); +#endif + } } } @@ -2053,6 +2064,9 @@ static void startRxPing (xref2osjob_t osjob) { // Decide what to do next for the MAC layer of a device static void engineUpdate (void) { +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: 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; diff --git a/lmic/radio.c b/lmic/radio.c index 043bda4..7ac25e6 100644 --- a/lmic/radio.c +++ b/lmic/radio.c @@ -527,6 +527,18 @@ static void txlora () { // now we actually start the transmission opmode(OPMODE_TX); + +#if LMIC_DEBUG_LEVEL > 0 + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + printf("%lu: TXMODE, freq=%lu, len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), LMIC.freq, LMIC.dataLen, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); +#endif } // start transmitter (buf=LMIC.frame, len=LMIC.dataLen) @@ -601,6 +613,24 @@ static void rxlora (u1_t rxmode) { } else { // continous rx (scan or rssi) opmode(OPMODE_RX); } + +#if LMIC_DEBUG_LEVEL > 0 + if (rxmode == RXMODE_RSSI) { + printf("RXMODE_RSSI\n"); + } else { + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + printf("%lu: %s, freq=%lu, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), + rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"), + LMIC.freq, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); + } +#endif } static void rxfsk (u1_t rxmode) { From e9d8a004d700b0401fccf6bc37481aa9f31dad27 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 15 Nov 2016 16:47:48 +0100 Subject: [PATCH 23/27] Add Arduino-specific files This adds an implementation of the LMIC HAL, plus some files needed for the Arduino library format and some examples. In addition, an export.sh script is added that can be used to generate an Arduino library from this repository. This script only reorders files, it does not change anything. When given the --link option, it creates symlinks instead of copies, allowing to test things in the Arduino IDE easily, while committing the original files in this repository. The HAL, examples and library.properties files are taken from the https://github.com/matthijskooijman/arduino-lmic repository, revision 90bc049 (Print downlink ack status in ttn-otaa example too). Only the configuration mechanism was changed slightly. --- lmic/oslmic.h | 7 + target/arduino/examples/raw/raw.ino | 162 +++++++++++ target/arduino/examples/ttn-abp/ttn-abp.ino | 226 ++++++++++++++++ target/arduino/examples/ttn-otaa/ttn-otaa.ino | 173 ++++++++++++ target/arduino/export.sh | 45 ++++ target/arduino/hal/hal.cpp | 253 ++++++++++++++++++ target/arduino/hal/hal.h | 28 ++ target/arduino/hal/target-config.h | 80 ++++++ target/arduino/library.properties | 9 + target/arduino/lmic.h | 9 + 10 files changed, 992 insertions(+) create mode 100644 target/arduino/examples/raw/raw.ino create mode 100644 target/arduino/examples/ttn-abp/ttn-abp.ino create mode 100644 target/arduino/examples/ttn-otaa/ttn-otaa.ino create mode 100755 target/arduino/export.sh create mode 100644 target/arduino/hal/hal.cpp create mode 100644 target/arduino/hal/hal.h create mode 100644 target/arduino/hal/target-config.h create mode 100644 target/arduino/library.properties create mode 100644 target/arduino/lmic.h diff --git a/lmic/oslmic.h b/lmic/oslmic.h index 698654a..f4c5b80 100644 --- a/lmic/oslmic.h +++ b/lmic/oslmic.h @@ -34,7 +34,14 @@ // You should not, however, change the lmic.[hc] #include +#ifdef ARDUINO +// When using the makefile, the target directory is put into the include +// path, so we can include target-config.h directly. When using Arduino, +// this is not the case, so we have to specify the path explicitly. +#include "../hal/target-config.h" +#else #include +#endif #ifdef __cplusplus extern "C"{ diff --git a/target/arduino/examples/raw/raw.ino b/target/arduino/examples/raw/raw.ino new file mode 100644 index 0000000..1e0382f --- /dev/null +++ b/target/arduino/examples/raw/raw.ino @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example transmits data on hardcoded channel and receives data + * when not transmitting. Running this sketch on two nodes should allow + * them to communicate. + *******************************************************************************/ + +#include +#include +#include + +#if !defined(DISABLE_INVERT_IQ_ON_RX) +#error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \ + config.h in the lmic library to set it. +#endif + +// How often to send a packet. Note that this sketch bypasses the normal +// LMIC duty cycle limiting, so when you change anything in this sketch +// (payload length, frequency, spreading factor), be sure to check if +// this interval should not also be increased. +// See this spreadsheet for an easy airtime and duty cycle calculator: +// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc +#define TX_INTERVAL 2000 + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +void onEvent (ev_t ev) { +} + +osjob_t txjob; +osjob_t timeoutjob; +static void tx_func (osjob_t* job); + +// Transmit the given string and call the given function afterwards +void tx(const char *str, osjobcb_t func) { + os_radio(RADIO_RST); // Stop RX first + delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet + LMIC.dataLen = 0; + while (*str) + LMIC.frame[LMIC.dataLen++] = *str++; + LMIC.osjob.func = func; + os_radio(RADIO_TX); + Serial.println("TX"); +} + +// Enable rx mode and call func when a packet is received +void rx(osjobcb_t func) { + LMIC.osjob.func = func; + LMIC.rxtime = os_getTime(); // RX _now_ + // Enable "continuous" RX (e.g. without a timeout, still stops after + // receiving a packet) + os_radio(RADIO_RXON); + Serial.println("RX"); +} + +static void rxtimeout_func(osjob_t *job) { + digitalWrite(LED_BUILTIN, LOW); // off +} + +static void rx_func (osjob_t* job) { + // Blink once to confirm reception and then keep the led on + digitalWrite(LED_BUILTIN, LOW); // off + delay(10); + digitalWrite(LED_BUILTIN, HIGH); // on + + // Timeout RX (i.e. update led status) after 3 periods without RX + os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func); + + // Reschedule TX so that it should not collide with the other side's + // next TX + os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func); + + Serial.print("Got "); + Serial.print(LMIC.dataLen); + Serial.println(" bytes"); + Serial.write(LMIC.frame, LMIC.dataLen); + Serial.println(); + + // Restart RX + rx(rx_func); +} + +static void txdone_func (osjob_t* job) { + rx(rx_func); +} + +// log text to USART and toggle LED +static void tx_func (osjob_t* job) { + // say hello + tx("Hello, world!", txdone_func); + // reschedule job every TX_INTERVAL (plus a bit of random to prevent + // systematic collisions), unless packets are received, then rx_func + // will reschedule at half this time. + os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func); +} + +// application entry point +void setup() { + Serial.begin(115200); + Serial.println("Starting"); + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + pinMode(LED_BUILTIN, OUTPUT); + + // initialize runtime env + os_init(); + + // Set up these settings once, and use them for both TX and RX + +#if defined(CFG_eu868) + // Use a frequency in the g3 which allows 10% duty cycling. + LMIC.freq = 869525000; +#elif defined(CFG_us915) + LMIC.freq = 902300000; +#endif + + // Maximum TX power + LMIC.txpow = 27; + // Use a medium spread factor. This can be increased up to SF12 for + // better range, but then the interval should be (significantly) + // lowered to comply with duty cycle limits as well. + LMIC.datarate = DR_SF9; + // This sets CR 4/5, BW125 (except for DR_SF7B, which uses BW250) + LMIC.rps = updr2rps(LMIC.datarate); + + Serial.println("Started"); + Serial.flush(); + + // setup initial job + os_setCallback(&txjob, tx_func); +} + +void loop() { + // execute scheduled jobs and events + os_runloop_once(); +} diff --git a/target/arduino/examples/ttn-abp/ttn-abp.ino b/target/arduino/examples/ttn-abp/ttn-abp.ino new file mode 100644 index 0000000..a8b4a18 --- /dev/null +++ b/target/arduino/examples/ttn-abp/ttn-abp.ino @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses ABP (Activation-by-personalisation), where a DevAddr and + * Session keys are preconfigured (unlike OTAA, where a DevEUI and + * application key is configured, while the DevAddr and session keys are + * assigned/generated in the over-the-air-activation procedure). + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + * + * To use this sketch, first register your application and device with + * the things network, to set or generate a DevAddr, NwkSKey and + * AppSKey. Each device should have their own unique values for these + * fields. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// LoRaWAN NwkSKey, network session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const PROGMEM u1_t NWKSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN AppSKey, application session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const u1_t PROGMEM APPSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN end-device address (DevAddr) +static const u4_t DEVADDR = 0x03FF0001 ; // <-- Change this address for every node! + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static uint8_t mydata[] = "Hello, world!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + default: + Serial.println(F("Unknown event")); + break; + } +} + +void do_send(osjob_t* j){ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { + Serial.begin(115200); + Serial.println(F("Starting")); + + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Set static session parameters. Instead of dynamically establishing a session + // by joining the network, precomputed session parameters are be provided. + #ifdef PROGMEM + // On AVR, these values are stored in flash and only copied to RAM + // once. Copy them to a temporary buffer here, LMIC_setSession will + // copy them into a buffer of its own again. + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); + #else + // If not running an AVR with PROGMEM, just use the arrays directly + LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); + #endif + + #if defined(CFG_eu868) + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + // NA-US channels 0-71 are configured automatically + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band + // TTN defines an additional channel at 869.525Mhz using SF9 for class B + // devices' ping slots. LMIC does not have an easy way to define set this + // frequency and support for class B is spotty and untested, so this + // frequency is not configured here. + #elif defined(CFG_us915) + // NA-US channels 0-71 are configured automatically + // but only one group of 8 should (a subband) should be active + // TTN recommends the second sub band, 1 in a zero based count. + // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json + LMIC_selectSubBand(1); + #endif + + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // TTN uses SF9 for its RX2 window. + LMIC.dn2Dr = DR_SF9; + + // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library) + LMIC_setDrTxpow(DR_SF7,14); + + // Start job + do_send(&sendjob); +} + +void loop() { + os_runloop_once(); +} diff --git a/target/arduino/examples/ttn-otaa/ttn-otaa.ino b/target/arduino/examples/ttn-otaa/ttn-otaa.ino new file mode 100644 index 0000000..d667aa2 --- /dev/null +++ b/target/arduino/examples/ttn-otaa/ttn-otaa.ino @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses OTAA (Over-the-air activation), where where a DevEUI and + * application key is configured, which are used in an over-the-air + * activation procedure where a DevAddr and session keys are + * assigned/generated for use with all further communication. + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + + * To use this sketch, first register your application and device with + * the things network, to set or generate an AppEUI, DevEUI and AppKey. + * Multiple devices can use the same AppEUI, but each device has its own + * DevEUI and AppKey. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// This EUI must be in little-endian format, so least-significant-byte +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, +// 0x70. +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);} + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);} + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +// The key shown here is the semtech default key. +static const u1_t PROGMEM APPKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; +void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);} + +static uint8_t mydata[] = "Hello, world!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + default: + Serial.println(F("Unknown event")); + break; + } +} + +void do_send(osjob_t* j){ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { + Serial.begin(9600); + Serial.println(F("Starting")); + + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Start job (sending automatically starts OTAA too) + do_send(&sendjob); +} + +void loop() { + os_runloop_once(); +} diff --git a/target/arduino/export.sh b/target/arduino/export.sh new file mode 100755 index 0000000..d05fcc3 --- /dev/null +++ b/target/arduino/export.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +SRC=$(cd "$(dirname "$0")"; pwd) + +usage() { + echo "$0 [--link] TARGET_DIR" + echo "Create an Arduino-compatible library in the given directory, overwriting existing files. If --link is given, creates symbolic links for easy testing." +} + + +CPOPTS= +case "$1" in + --help) + usage + exit 0 + ;; + --link) + CPOPTS=--symbolic-link + shift; + ;; + --*) + echo "Unknown option: $1" 1>&2 + exit 1 + ;; +esac + +TARGET=$1 + +if [ -z "$TARGET" ]; then + usage + exit 1 +fi + +if ! [ -d "$(dirname "$TARGET")" ]; then + echo "Parent of $TARGET should exist" 1>&2 + exit 1 +fi + +mkdir -p "$TARGET"/src +cp $CPOPTS -f -v "$SRC"/library.properties "$TARGET" +cp $CPOPTS -f -v "$SRC"/lmic.h "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/../../lmic "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/../../aes "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/hal "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/examples "$TARGET" diff --git a/target/arduino/hal/hal.cpp b/target/arduino/hal/hal.cpp new file mode 100644 index 0000000..53bf359 --- /dev/null +++ b/target/arduino/hal/hal.cpp @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ + +#include +#include +#include "../lmic.h" +#include "hal.h" +#include + +// ----------------------------------------------------------------------------- +// I/O + +static void hal_io_init () { + // NSS and DIO0 are required, DIO1 is required for LoRa, DIO2 for FSK + ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[0] != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] != LMIC_UNUSED_PIN || lmic_pins.dio[2] != LMIC_UNUSED_PIN); + + pinMode(lmic_pins.nss, OUTPUT); + if (lmic_pins.rxtx != LMIC_UNUSED_PIN) + pinMode(lmic_pins.rxtx, OUTPUT); + if (lmic_pins.rst != LMIC_UNUSED_PIN) + pinMode(lmic_pins.rst, OUTPUT); + + pinMode(lmic_pins.dio[0], INPUT); + if (lmic_pins.dio[1] != LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[1], INPUT); + if (lmic_pins.dio[2] != LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[2], INPUT); +} + +// val == 1 => tx 1 +void hal_pin_rxtx (u1_t val) { + if (lmic_pins.rxtx != LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.rxtx, val); +} + +// set radio RST pin to given value (or keep floating!) +void hal_pin_rst (u1_t val) { + if (lmic_pins.rst == LMIC_UNUSED_PIN) + return; + + if(val == 0 || val == 1) { // drive pin + pinMode(lmic_pins.rst, OUTPUT); + digitalWrite(lmic_pins.rst, val); + } else { // keep pin floating + pinMode(lmic_pins.rst, INPUT); + } +} + +static bool dio_states[NUM_DIO] = {0}; + +static void hal_io_check() { + uint8_t i; + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] == LMIC_UNUSED_PIN) + continue; + + if (dio_states[i] != digitalRead(lmic_pins.dio[i])) { + dio_states[i] = !dio_states[i]; + if (dio_states[i]) + radio_irq_handler(i); + } + } +} + +// ----------------------------------------------------------------------------- +// SPI + +static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0); + +static void hal_spi_init () { + SPI.begin(); +} + +void hal_pin_nss (u1_t val) { + if (!val) + SPI.beginTransaction(settings); + else + SPI.endTransaction(); + + //Serial.println(val?">>":"<<"); + digitalWrite(lmic_pins.nss, val); +} + +// perform SPI transaction with radio +u1_t hal_spi (u1_t out) { + u1_t res = SPI.transfer(out); +/* + Serial.print(">"); + Serial.print(out, HEX); + Serial.print("<"); + Serial.println(res, HEX); + */ + return res; +} + +// ----------------------------------------------------------------------------- +// TIME + +static void hal_time_init () { + // Nothing to do +} + +u4_t hal_ticks () { + // Because micros() is scaled down in this function, micros() will + // overflow before the tick timer should, causing the tick timer to + // miss a significant part of its values if not corrected. To fix + // this, the "overflow" serves as an overflow area for the micros() + // counter. It consists of three parts: + // - The US_PER_OSTICK upper bits are effectively an extension for + // the micros() counter and are added to the result of this + // function. + // - The next bit overlaps with the most significant bit of + // micros(). This is used to detect micros() overflows. + // - The remaining bits are always zero. + // + // By comparing the overlapping bit with the corresponding bit in + // the micros() return value, overflows can be detected and the + // upper bits are incremented. This is done using some clever + // bitwise operations, to remove the need for comparisons and a + // jumps, which should result in efficient code. By avoiding shifts + // other than by multiples of 8 as much as possible, this is also + // efficient on AVR (which only has 1-bit shifts). + static uint8_t overflow = 0; + + // Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0, + // the others will be the lower bits of our return value. + uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT; + // Most significant byte of scaled + uint8_t msb = scaled >> 24; + // Mask pointing to the overlapping bit in msb and overflow. + const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT)); + // Update overflow. If the overlapping bit is different + // between overflow and msb, it is added to the stored value, + // so the overlapping bit becomes equal again and, if it changed + // from 1 to 0, the upper bits are incremented. + overflow += (msb ^ overflow) & mask; + + // Return the scaled value with the upper bits of stored added. The + // overlapping bit will be equal and the lower bits will be 0, so + // bitwise or is a no-op for them. + return scaled | ((uint32_t)overflow << 24); + + // 0 leads to correct, but overly complex code (it could just return + // micros() unmodified), 8 leaves no room for the overlapping bit. + static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value"); +} + +// Returns the number of ticks until time. Negative values indicate that +// time has already passed. +static s4_t delta_time(u4_t time) { + return (s4_t)(time - hal_ticks()); +} + +void hal_waitUntil (u4_t time) { + s4_t delta = delta_time(time); + // From delayMicroseconds docs: Currently, the largest value that + // will produce an accurate delay is 16383. + while (delta > (16000 / US_PER_OSTICK)) { + delay(16); + delta -= (16000 / US_PER_OSTICK); + } + if (delta > 0) + delayMicroseconds(delta * US_PER_OSTICK); +} + +// check and rewind for target time +u1_t hal_checkTimer (u4_t time) { + // No need to schedule wakeup, since we're not sleeping + return delta_time(time) <= 0; +} + +static uint8_t irqlevel = 0; + +void hal_disableIRQs () { + noInterrupts(); + irqlevel++; +} + +void hal_enableIRQs () { + if(--irqlevel == 0) { + interrupts(); + + // Instead of using proper interrupts (which are a bit tricky + // and/or not available on all pins on AVR), just poll the pin + // values. Since os_runloop disables and re-enables interrupts, + // putting this here makes sure we check at least once every + // loop. + // + // As an additional bonus, this prevents the can of worms that + // we would otherwise get for running SPI transfers inside ISRs + hal_io_check(); + } +} + +void hal_sleep () { + // Not implemented +} + +// ----------------------------------------------------------------------------- + +#if defined(LMIC_PRINTF_TO) +static int uart_putchar (char c, FILE *) +{ + LMIC_PRINTF_TO.write(c) ; + return 0 ; +} + +void hal_printf_init() { + // create a FILE structure to reference our UART output function + static FILE uartout; + memset(&uartout, 0, sizeof(uartout)); + + // fill in the UART file descriptor with pointer to writer. + fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); + + // The uart is the standard output device STDOUT. + stdout = &uartout ; +} +#endif // defined(LMIC_PRINTF_TO) + +void hal_init () { + // configure radio I/O and interrupt handler + hal_io_init(); + // configure radio SPI + hal_spi_init(); + // configure timer and interrupt handler + hal_time_init(); +#if defined(LMIC_PRINTF_TO) + // printf support + hal_printf_init(); +#endif +} + +void hal_failed (const char *file, u2_t line) { +#if defined(LMIC_FAILURE_TO) + LMIC_FAILURE_TO.println("FAILURE "); + LMIC_FAILURE_TO.print(file); + LMIC_FAILURE_TO.print(':'); + LMIC_FAILURE_TO.println(line); + LMIC_FAILURE_TO.flush(); +#endif + hal_disableIRQs(); + while(1); +} diff --git a/target/arduino/hal/hal.h b/target/arduino/hal/hal.h new file mode 100644 index 0000000..e096569 --- /dev/null +++ b/target/arduino/hal/hal.h @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ +#ifndef _hal_hal_h_ +#define _hal_hal_h_ + +static const int NUM_DIO = 3; + +struct lmic_pinmap { + u1_t nss; + u1_t rxtx; + u1_t rst; + u1_t dio[NUM_DIO]; +}; + +// Use this for any unused pins. +const u1_t LMIC_UNUSED_PIN = 0xff; + +// Declared here, to be defined an initialized by the application +extern const lmic_pinmap lmic_pins; + +#endif // _hal_hal_h_ diff --git a/target/arduino/hal/target-config.h b/target/arduino/hal/target-config.h new file mode 100644 index 0000000..026a18c --- /dev/null +++ b/target/arduino/hal/target-config.h @@ -0,0 +1,80 @@ +#ifndef _lmic_arduino_hal_config_h_ +#define _lmic_arduino_hal_config_h_ + +#define CFG_eu868 1 +//#define CFG_us915 1 + +// This is the SX1272/SX1273 radio, which is also used on the HopeRF +// RFM92 boards. +//#define CFG_sx1272_radio 1 +// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on +// the HopeRF RFM95 boards. +#define CFG_sx1276_radio 1 + +// 16 μs per tick +// LMIC requires ticks to be 15.5μs - 100 μs long +#define US_PER_OSTICK_EXPONENT 4 +#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT) +#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) + +// Enable this to allow using printf() to print to the given serial port +// (or any other Print object). This can be easy for debugging. The +// current implementation only works on AVR, though. +//#define LMIC_PRINTF_TO Serial + +// Any runtime assertion failures are printed to this serial port (or +// any other Print object). If this is unset, any failures just silently +// halt execution. +#define LMIC_FAILURE_TO Serial + +// Set this to 1 to enable some basic debug output (using printf) about +// RF settings used during transmission and reception. Set to 2 to +// enable more verbose output. Make sure that printf is actually +// configured (e.g. on AVR it is not by default), otherwise using it can +// cause crashing. +#define LMIC_DEBUG_LEVEL 0 + +// Uncomment this to disable all code related to joining +//#define DISABLE_JOIN +// Uncomment this to disable all code related to ping +#define DISABLE_PING +// Uncomment this to disable all code related to beacon tracking. +// Requires ping to be disabled too +#define DISABLE_BEACONS + +// Uncomment these to disable the corresponding MAC commands. +// Class A +//#define DISABLE_MCMD_DCAP_REQ // duty cycle cap +//#define DISABLE_MCMD_DN2P_SET // 2nd DN window param +//#define DISABLE_MCMD_SNCH_REQ // set new channel +// Class B +//#define DISABLE_MCMD_PING_SET // set ping freq, automatically disabled by DISABLE_PING +//#define DISABLE_MCMD_BCNI_ANS // next beacon start, automatical disabled by DISABLE_BEACON + +// In LoRaWAN, a gateway applies I/Q inversion on TX, and nodes do the +// same on RX. This ensures that gateways can talk to nodes and vice +// versa, but gateways will not hear other gateways and nodes will not +// hear other nodes. By uncommenting this macro, this inversion is +// disabled and this node can hear other nodes. If two nodes both have +// this macro set, they can talk to each other (but they can no longer +// hear gateways). This should probably only be used when debugging +// and/or when talking to the radio directly (e.g. like in the "raw" +// example). +//#define DISABLE_INVERT_IQ_ON_RX + +// This allows choosing between multiple included AES implementations. +// Make sure exactly one of these is uncommented. +// +// This selects the original AES implementation included LMIC. This +// implementation is optimized for speed on 32-bit processors using +// fairly big lookup tables, but it takes up big amounts of flash on the +// AVR architecture. +// #define USE_ORIGINAL_AES +// +// This selects the AES implementation written by Ideetroon for their +// own LoRaWAN library. It also uses lookup tables, but smaller +// byte-oriented ones, making it use a lot less flash space (but it is +// also about twice as slow as the original). +#define USE_IDEETRON_AES + +#endif // _lmic_arduino_hal_config_h_ diff --git a/target/arduino/library.properties b/target/arduino/library.properties new file mode 100644 index 0000000..0dbf7d5 --- /dev/null +++ b/target/arduino/library.properties @@ -0,0 +1,9 @@ +name=IBM LMIC framework +version=1.5.0+arduino-1 +author=IBM +maintainer=Matthijs Kooijman +sentence=Arduino port of the LMIC (LoraWAN-in-C, formerly LoraMAC-in-C) framework provided by IBM. +paragraph=Supports SX1272/SX1276 and HopeRF RFM92/RFM95 tranceivers +category=Communication +url=http://www.research.ibm.com/labs/zurich/ics/lrsc/lmic.html +architectures=* diff --git a/target/arduino/lmic.h b/target/arduino/lmic.h new file mode 100644 index 0000000..f2c7935 --- /dev/null +++ b/target/arduino/lmic.h @@ -0,0 +1,9 @@ +#ifdef __cplusplus +extern "C"{ +#endif + +#include "lmic/lmic.h" + +#ifdef __cplusplus +} +#endif From 47f7984aec22541b590930c032a5e885366f2f2c Mon Sep 17 00:00:00 2001 From: Thomas Telkamp Date: Mon, 28 Nov 2016 13:55:15 +0100 Subject: [PATCH 24/27] Assign frequenties between 865-868Mhz to the right band See CEPT ERC Recommendation 70-03, Annex 1, band g1, note 5. --- lmic/lmic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index d520dc9..502bd10 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -601,7 +601,7 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { if( band == -1 ) { if( freq >= 869400000 && freq <= 869650000 ) freq |= BAND_DECI; // 10% 27dBm - else if( (freq >= 868000000 && freq <= 868600000) || + else if( (freq >= 865000000 && freq <= 868600000) || (freq >= 869700000 && freq <= 870000000) ) freq |= BAND_CENTI; // 1% 14dBm else From be2868bd53d423d6031f1b4000018f587c5ff724 Mon Sep 17 00:00:00 2001 From: Thomas Telkamp Date: Mon, 28 Nov 2016 14:18:51 +0100 Subject: [PATCH 25/27] Added Link Check commands Implementation of LoRaWAN Link Check commands (LinkCheckReq, LinkCheckAns). --- lmic/lmic.c | 9 +++++++++ lmic/lmic.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/lmic/lmic.c b/lmic/lmic.c index 502bd10..9bc19fe 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1147,6 +1147,10 @@ static bit_t decodeFrame (void) { case MCMD_LCHK_ANS: { //int gwmargin = opts[oidx+1]; //int ngws = opts[oidx+2]; + LMIC.LinkCheckMargin = opts[oidx+1]; + LMIC.LinkCheckGwCnt = opts[oidx+2]; + LMIC.LinkCheckReq = 0; + LMIC.LinkCheckAns = 1; oidx += 3; continue; } @@ -1662,6 +1666,11 @@ static void buildDataFrame (void) { end += 2; LMIC.ladrAns = 0; } + if( LMIC.LinkCheckReq ) { //Link Check Request + LMIC.frame[end+0] = MCMD_LCHK_REQ; + end += 1; + LMIC.LinkCheckAns = 0; + } #if !defined(DISABLE_BEACONS) if( LMIC.bcninfoTries > 0 ) { LMIC.frame[end] = MCMD_BCNI_REQ; diff --git a/lmic/lmic.h b/lmic/lmic.h index 8ad61f0..87d7a44 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -235,6 +235,10 @@ struct lmic_t { u1_t margin; bit_t ladrAns; // link adr adapt answer pending bit_t devsAns; // device status answer pending + u1_t LinkCheckReq; // send LinkCheckReq command + u1_t LinkCheckAns; // LinkCheckAns received + u1_t LinkCheckMargin; // link margin in dB of the last successfully received LinkCheckReq + u1_t LinkCheckGwCnt; // number of gateways that successfully received the last LinkCheckReq u1_t adrEnabled; u1_t moreData; // NWK has more data pending #if !defined(DISABLE_MCMD_DCAP_REQ) From fbb7eae8c168fe3348dd257aceb75b3e5b382459 Mon Sep 17 00:00:00 2001 From: Thomas Telkamp Date: Mon, 28 Nov 2016 14:22:38 +0100 Subject: [PATCH 26/27] Fix incorrect MCMD_DCAP_REQ parsing --- lmic/lmic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 9bc19fe..6a8bfa4 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -1215,8 +1215,8 @@ static bit_t decodeFrame (void) { LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); LMIC.dutyCapAns = 1; - oidx += 2; #endif // !DISABLE_MCMD_DCAP_REQ + oidx += 2; continue; } case MCMD_SNCH_REQ: { From 3ace32bb05201f64cc2c35a01bfcd3e00e9393cc Mon Sep 17 00:00:00 2001 From: Thomas Telkamp Date: Mon, 28 Nov 2016 14:28:16 +0100 Subject: [PATCH 27/27] Change join duty-cycle limit to 1% The explicit 0.1% duty cycle limit on join was removed in LoRaWAN 1.0.2. --- lmic/lmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lmic/lmic.c b/lmic/lmic.c index 6a8bfa4..752cf36 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -552,8 +552,8 @@ void LMIC_setPingable (u1_t intvExp) { // enum { NUM_DEFAULT_CHANNELS=3 }; static CONST_TABLE(u4_t, iniChannelFreq)[6] = { - // Join frequencies and duty cycle limit (0.1%) - EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI, + // Join frequencies and duty cycle limit (1%) + EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI, // Default operational frequencies EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI, };