diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0d7d6b1..f3c3fc3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -22,14 +22,15 @@ "${workspaceRoot}/examples/send_recv/build/include", "${workspaceRoot}/include", "${workspaceRoot}/src" - ], + ], "defines": [ "_DEBUG" ], "compilerPath": "/usr/bin/clang", "cStandard": "c11", "cppStandard": "c++11", - "intelliSenseMode": "clang-x64" + "intelliSenseMode": "clang-x64", + "compileCommands": "${workspaceFolder}/examples/hello_world/build/compile_commands.json" } ], "version": 4 diff --git a/LICENSE b/LICENSE index 2dc34d6..51aa2a6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Manuel Bleichenbacher +Copyright (c) 2018-2021 Manuel Bleichenbacher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1db5c03..8eb181a 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,10 @@ This ESP32 component provides LoRaWAN communication with [The Things Network](ht The library is based on the LMIC library from IBM (specifically the well-maintained version by MCCI – see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network. -## New in version 3.3 +## New in version 4.0 -- Verified compatibility with ESP-IDF v4.2 -- Upgraded underlying library mcci-catena/arduino-lmic to v3.3.0 (no relevant changes) -- Ensure interrupt code is in IRAM +- Verified compatibility with ESP-IDF v4.3 +- Upgraded underlying library mcci-catena/arduino-lmic to v4.0.1 (improved channel shuffling) ## Get Started diff --git a/library.json b/library.json index 2ee6c91..9c3bce4 100644 --- a/library.json +++ b/library.json @@ -13,7 +13,7 @@ "url": "https://github.com/manuelbl/ttn-esp32.git", "branch": "master" }, - "version": "3.3.0", + "version": "4.0.0-pre", "license": "MIT License", "export": { "include": [ diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index 957d622..472e62c 100644 --- a/src/lmic/lmic.c +++ b/src/lmic/lmic.c @@ -1476,7 +1476,12 @@ ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms // a compile-time configuration. (In other words, assume that millis() // clock is accurate to 0.1%.) You should never use clockerror to // compensate for system-late problems. - u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / (1000 * 1000); + // note about compiler: The initializer for maxError is coded for + // maximum portability. On 16-bit systems, some compilers complain + // if we write x / (1000 * 1000). x / 1000 / 1000 uses constants, + // is generally acceptable so it can be optimized in compiler's own + // way. + u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / 1000 / 1000; if (! LMIC_ENABLE_arbitrary_clock_error && clockerr > maxError) { clockerr = maxError; @@ -1616,21 +1621,9 @@ static bit_t processJoinAccept (void) { // initDefaultChannels(0) for EU-like, nothing otherwise LMICbandplan_joinAcceptChannelClear(); - if (!LMICbandplan_hasJoinCFlist() && dlen > LEN_JA) { - // if no JoinCFList, we're supposed to continue - // the join per 2.2.5 of LoRaWAN regional 2.2.4 - // https://github.com/mcci-catena/arduino-lmic/issues/19 - } else if ( LMICbandplan_hasJoinCFlist() && dlen > LEN_JA ) { - dlen = OFF_CFLIST; - for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { - u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]); - if( freq ) { - LMIC_setupChannel(chidx, freq, 0, -1); -#if LMIC_DEBUG_LEVEL > 1 - LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq); -#endif - } - } + // process the CFList if present + if (dlen == LEN_JAEXT) { + LMICbandplan_processJoinAcceptCFList(); } // already incremented when JOIN REQ got sent off @@ -2882,7 +2875,11 @@ dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) { } static bit_t isTxPathBusy(void) { - return (LMIC.opmode & (OP_TXDATA|OP_JOINING)) != 0; + return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0; +} + +bit_t LMIC_queryTxReady (void) { + return ! isTxPathBusy(); } static bit_t adjustDrForFrameIfNotBusy(u1_t len) { @@ -2902,6 +2899,10 @@ void LMIC_setTxData (void) { } void LMIC_setTxData_strict (void) { + if (isTxPathBusy()) { + return; + } + LMICOS_logEventUint32(__func__, ((u4_t)LMIC.pendTxPort << 24u) | ((u4_t)LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u)); LMIC.opmode |= OP_TXDATA; if( (LMIC.opmode & OP_JOINING) == 0 ) { @@ -2920,7 +2921,7 @@ lmic_tx_error_t LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t conf // send a message w/o callback; do not adjust data rate lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { - if ( LMIC.opmode & OP_TXDATA ) { + if (isTxPathBusy()) { // already have a message queued return LMIC_ERROR_TX_BUSY; } @@ -2940,7 +2941,7 @@ lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1 return LMIC_ERROR_TX_FAILED; } } - return 0; + return LMIC_ERROR_SUCCESS; } // send a message with callback; try to adjust data rate diff --git a/src/lmic/lmic.h b/src/lmic/lmic.h index 471f1f9..49703ae 100644 --- a/src/lmic/lmic.h +++ b/src/lmic/lmic.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2016 Matthijs Kooijman. - * Copyright (c) 2016-2020 MCCI Corporation. + * Copyright (c) 2016-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -96,7 +96,7 @@ extern "C"{ #endif -// LMIC version -- this is ths IBM LMIC version +// LMIC version -- this is the IBM LMIC version #define LMIC_VERSION_MAJOR 1 #define LMIC_VERSION_MINOR 6 #define LMIC_VERSION_BUILD 1468577746 @@ -105,7 +105,8 @@ extern "C"{ #define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \ ((((major)*UINT32_C(1)) << 24) | (((minor)*UINT32_C(1)) << 16) | (((patch)*UINT32_C(1)) << 8) | (((local)*UINT32_C(1)) << 0)) -#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(3, 3, 0, 0) /* v3.3.0 */ +#define ARDUINO_LMIC_VERSION \ + ARDUINO_LMIC_VERSION_CALC(4, 0, 1, 1) /* 4.0.1-pre1 */ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ ((((v)*UINT32_C(1)) >> 24u) & 0xFFu) @@ -119,10 +120,35 @@ extern "C"{ #define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \ ((v) & 0xFFu) +/// \brief convert a semantic version to an ordinal integer. +#define ARDUINO_LMIC_VERSION_TO_ORDINAL(v) \ + (((v) & 0xFFFFFF00u) | (((v) - 1) & 0xFFu)) + +/// \brief compare two semantic versions +/// \return \c true if \p a is less than \p b (as a semantic version). +#define ARDUINO_LMIC_VERSION_COMPARE_LT(a, b) \ + (ARDUINO_LMIC_VERSION_TO_ORDINAL(a) < ARDUINO_LMIC_VERSION_TO_ORDINAL(b)) + +/// \brief compare two semantic versions +/// \return \c true if \p a is less than or equal to \p b (as a semantic version). +#define ARDUINO_LMIC_VERSION_COMPARE_LE(a, b) \ + (ARDUINO_LMIC_VERSION_TO_ORDINAL(a) <= ARDUINO_LMIC_VERSION_TO_ORDINAL(b)) + +/// \brief compare two semantic versions +/// \return \c true if \p a is greater than \p b (as a semantic version). +#define ARDUINO_LMIC_VERSION_COMPARE_GT(a, b) \ + (ARDUINO_LMIC_VERSION_TO_ORDINAL(a) > ARDUINO_LMIC_VERSION_TO_ORDINAL(b)) + +/// \brief compare two semantic versions +/// \return \c true if \p a is greater than or equal to \p b (as a semantic version). +#define ARDUINO_LMIC_VERSION_COMPARE_GE(a, b) \ + (ARDUINO_LMIC_VERSION_TO_ORDINAL(a) >= ARDUINO_LMIC_VERSION_TO_ORDINAL(b)) + + //! Only For Antenna Tuning Tests ! //#define CFG_TxContinuousMode 1 -// since this was annouunced as the API variable, we keep it. But it's not used, +// since this was announced as the API variable, we keep it. But it's not used, // MAX_LEN_FRAME is what the code uses. enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length @@ -131,10 +157,10 @@ enum { MAX_MISSED_BCNS = (2 * 60 * 60 + 127) / 128 }; //!< threshold for d // note that we need 100 ppm timing accuracy for // this, to keep the timing error to +/- 700ms. enum { MAX_RXSYMS = 350 }; // Stop tracking beacon if sync error grows beyond this. A 0.4% clock error - // at SF9.125k means 512 ms; one sybol is 4.096 ms, + // at SF9.125k means 512 ms; one symbol is 4.096 ms, // so this needs to be at least 125 for an STM32L0. // And for 100ppm clocks and 2 hours of beacon misses, - // this needs to accomodate 1.4 seconds of error at + // this needs to accommodate 1.4 seconds of error at // 4.096 ms/sym or at least 342 symbols. enum { LINK_CHECK_CONT = 0 , // continue with this after reported dead link @@ -161,7 +187,7 @@ struct band_t { u2_t txcap; // duty cycle limitation: 1/txcap s1_t txpow; // maximum TX power u1_t lastchnl; // last used channel - ostime_t avail; // channel is blocked until this time + ostime_t avail; // band is blocked until this time }; TYPEDEF_xref2band_t; //!< \internal @@ -172,10 +198,8 @@ struct lmic_saved_adr_state_s { #elif CFG_LMIC_US_like // US915 spectrum ================================================= -enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable - struct lmic_saved_adr_state_s { - u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits + u2_t channelMap[(72+15)/16]; // enabled bits u2_t activeChannels125khz; u2_t activeChannels500khz; }; @@ -531,10 +555,10 @@ struct lmic_t { // bit map of enabled datarates for each channel u2_t channelDrMap[MAX_CHANNELS]; u2_t channelMap; + u2_t channelShuffleMap; #elif CFG_LMIC_US_like - u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater) - u2_t xchDrMap[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto - u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits + u2_t channelMap[(72+15)/16]; // enabled bits + u2_t channelShuffleMap[(72+15)/16]; // enabled bits u2_t activeChannels125khz; u2_t activeChannels500khz; #endif @@ -569,7 +593,10 @@ struct lmic_t { u1_t txChnl; // channel for next TX u1_t globalDutyRate; // max rate: 1/2^k - +#if CFG_LMIC_US_like + u1_t txChnl_125kHz; ///< during joins on 500 kHz, the 125 kHz channel + /// that was last used. +#endif u1_t upRepeat; // configured up repeat s1_t adrTxPow; // ADR adjusted TX power u1_t datarate; // current data rate @@ -659,6 +686,12 @@ bit_t LMIC_enableChannel(u1_t channel); bit_t LMIC_disableSubBand(u1_t band); bit_t LMIC_selectSubBand(u1_t band); +//! \brief get the number of (fixed) default channels before the programmable channels. +u1_t LMIC_queryNumDefaultChannels(void); + +//! \brief check whether the LMIC is ready for a transmit packet +bit_t LMIC_queryTxReady(void); + 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) @@ -705,6 +738,8 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference); int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData); int LMIC_registerEventCb(lmic_event_cb_t *pEventCb, void *pUserData); +int LMIC_findNextChannel(uint16_t *, const uint16_t *, uint16_t, int); + // APIs for client half of compliance. typedef u1_t lmic_compliance_rx_action_t; diff --git a/src/lmic/lmic_as923.c b/src/lmic/lmic_as923.c index f46f50b..5366d71 100644 --- a/src/lmic/lmic_as923.c +++ b/src/lmic/lmic_as923.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; +bit_t +LMICas923_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + // see table in 2.7.6 -- this assumes UplinkDwellTime = 0. static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = { 59+5, // [0] @@ -226,17 +234,29 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { return 1; } + +/// +/// \brief query number of default channels. +/// +u1_t LMIC_queryNumDefaultChannels() { + return NUM_DEFAULT_CHANNELS; +} + +/// +/// \brief LMIC_setupChannel for EU 868 +/// +/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range +/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS. +/// This routine is used internally for MAC commands, so we enforce +/// this for the extenal API as well. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { // zero the band bits in freq, just in case. freq &= ~3; if (chidx < NUM_DEFAULT_CHANNELS) { - // can't disable a default channel. - if (freq == 0) - return 0; - // can't change a default channel. - else if (freq != (LMIC.channelFreq[chidx] & ~3)) - return 0; + // can't do anything to a default channel. + return 0; } bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) @@ -315,36 +335,109 @@ void LMICas923_setRx1Params(void) { LMIC.rps = dndr2rps(LMIC.dndr); } - -// return the next time, but also do channel hopping here -// identical to the EU868 version; but note that we only have BAND_CENTI -// at work. +/// +/// \brief change the TX channel given the desired tx time. +/// +/// \param [in] now is the time at which we want to transmit. In fact, it's always +/// the current time. +/// +/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the +/// selected channel. +/// +/// \details +/// We scan all the bands, creating a mask of all enabled channels that are +/// feasible at the earliest possible time. We then randomly choose one from +/// that, updating the shuffle mask. +/// +/// \note +/// identical to the EU868 version; but note that we only have BAND_CENTI +/// in AS923. +/// ostime_t LMICas923_nextTx(ostime_t now) { - u1_t bmap = 0xF; - do { - ostime_t mintime = now + /*8h*/sec2osticks(28800); - u1_t band = 0; - for (u1_t bi = 0; bi<4; bi++) { - if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0) - mintime = LMIC.bands[band = bi].avail; - } - // Find next channel in given band - u1_t chnl = LMIC.bands[band].lastchnl; - for (u1_t ci = 0; ci= MAX_CHANNELS) - chnl -= MAX_CHANNELS; - if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled - (LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && - band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band - LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; - return mintime; - } - } - if ((bmap &= ~(1 << band)) == 0) { - // No feasible channel found! - return mintime; - } - } while (1); + ostime_t mintime = now + /*8h*/sec2osticks(28800); + u2_t availMap; + u2_t feasibleMap; + u1_t bandMap; + + // set mintime to the earliest time of all enabled channels + // (can't just look at bands); and for a given channel, we + // can't tell if we're ready till we've checked all possible + // avail times. + bandMap = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + u2_t chnlBit = 1 << chnl; + + // none at any higher numbers? + if (LMIC.channelMap < chnlBit) + break; + + // not enabled? + if ((LMIC.channelMap & chnlBit) == 0) + continue; + + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + + u1_t const band = LMIC.channelFreq[chnl] & 0x3; + u1_t const thisBandBit = 1 << band; + // already considered? + if ((bandMap & thisBandBit) != 0) + continue; + + // consider this band. + bandMap |= thisBandBit; + + // enabled, not considered, feasible: adjust the min time. + if ((s4_t)(mintime - LMIC.bands[band].avail) > 0) + mintime = LMIC.bands[band].avail; + } + + // make a mask of candidates available for use + availMap = 0; + feasibleMap = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + u2_t chnlBit = 1 << chnl; + + // none at any higher numbers? + if (LMIC.channelMap < chnlBit) + break; + + // not enabled? + if ((LMIC.channelMap & chnlBit) == 0) + continue; + + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + + // This channel is feasible. But might not be available. + feasibleMap |= chnlBit; + + // not available yet? + u1_t const band = LMIC.channelFreq[chnl] & 0x3; + if ((s4_t)(LMIC.bands[band].avail - mintime) > 0) + continue; + + // ok: this is a candidate. + availMap |= chnlBit; + } + + // find the next available chennel. + u2_t saveShuffleMap = LMIC.channelShuffleMap; + int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl); + + // restore bits in the shuffleMap that were on, but might have reset + // if availMap was used to refresh shuffleMap. These are channels that + // are feasble but not yet candidates due to band saturation + LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap; + + if (candidateCh >= 0) { + // update the channel; otherwise we'll just use the + // most recent one. + LMIC.txChnl = candidateCh; + } + return mintime; } #if !defined(DISABLE_BEACONS) diff --git a/src/lmic/lmic_au915.c b/src/lmic/lmic_au915.c index 08df1ad..301cfc7 100644 --- a/src/lmic/lmic_au915.c +++ b/src/lmic/lmic_au915.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; +bit_t +LMICau915_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = { 59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 0, 61+5, 137+5, 250+5, 250+5, 250+5, 250+5 }; @@ -144,7 +152,24 @@ u4_t LMICau915_convFreq(xref2cu1_t ptr) { return freq; } -// au915: no support for xchannels. +/// +/// \brief query number of default channels. +/// +/// \note +/// For AU, we have no programmable channels; all channels +/// are fixed. Return the total channel count. +/// +u1_t LMIC_queryNumDefaultChannels() { + return 64 + 8; +} + + +/// +/// \brief LMIC_setupChannel for AU915 +/// +/// \note there are no progammable channels for US915, so this API +/// always returns FALSE. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { LMIC_API_PARAMETER(chidx); LMIC_API_PARAMETER(freq); diff --git a/src/lmic/lmic_bandplan.h b/src/lmic/lmic_bandplan.h index db5863d..3921819 100644 --- a/src/lmic/lmic_bandplan.h +++ b/src/lmic/lmic_bandplan.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -166,6 +166,14 @@ # error "LMICbandplan_isDataRateFeasible() not defined by bandplan" #endif +#if !defined(LMICbandplan_validDR) +# error "LMICbandplan_validDR() not defined by bandplan" +#endif + +#if !defined(LMICbandplan_processJoinAcceptCFList) +# error "LMICbandplan_processJoinAcceptCFList() not defined by bandplan" +#endif + // // Things common to lmic.c code // @@ -228,4 +236,22 @@ ostime_t LMICcore_rndDelay(u1_t secSpan); void LMICcore_setDrJoin(u1_t reason, u1_t dr); ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in); +// this has been exported to clients forever by lmic.h. including lorabase.h; +// but with multiband lorabase can't really safely do this; it's really an LMIC-ism. +// As are the rest of the static inlines.. + +///< \brief return non-zero if given DR is valid for this region. +static inline bit_t validDR (dr_t dr) { return LMICbandplan_validDR(dr); } // in range + +///< \brief region-specific table mapping DR to RPS/CRC bits; index by dr+1 +extern CONST_TABLE(u1_t, _DR2RPS_CRC)[]; + +static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); } +static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); } +static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate +static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate +static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR +static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps + + #endif // _lmic_bandplan_h_ diff --git a/src/lmic/lmic_bandplan_as923.h b/src/lmic/lmic_bandplan_as923.h index 16f4518..69bb83d 100644 --- a/src/lmic/lmic_bandplan_as923.h +++ b/src/lmic/lmic_bandplan_as923.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -108,4 +108,8 @@ void LMICas923_updateTx(ostime_t txbeg); ostime_t LMICas923_nextJoinTime(ostime_t now); #define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now) +#undef LMICbandplan_validDR +bit_t LMICas923_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICas923_validDR(dr) + #endif // _lmic_bandplan_as923_h_ diff --git a/src/lmic/lmic_bandplan_au915.h b/src/lmic/lmic_bandplan_au915.h index f17194b..7863779 100644 --- a/src/lmic/lmic_bandplan_au915.h +++ b/src/lmic/lmic_bandplan_au915.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,4 +66,8 @@ void LMICau915_setRx1Params(void); void LMICau915_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(txbeg) LMICau915_updateTx(txbeg) +#undef LMICbandplan_validDR +bit_t LMICau915_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICau915_validDR(dr) + #endif // _lmic_bandplan_au915_h_ diff --git a/src/lmic/lmic_bandplan_eu868.h b/src/lmic/lmic_bandplan_eu868.h index efff7d5..83ec405 100644 --- a/src/lmic/lmic_bandplan_eu868.h +++ b/src/lmic/lmic_bandplan_eu868.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -88,4 +88,8 @@ ostime_t LMICeu868_nextJoinTime(ostime_t now); void LMICeu868_setRx1Params(void); #define LMICbandplan_setRx1Params() LMICeu868_setRx1Params() +#undef LMICbandplan_validDR +bit_t LMICeu868_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICeu868_validDR(dr) + #endif // _lmic_eu868_h_ diff --git a/src/lmic/lmic_bandplan_in866.h b/src/lmic/lmic_bandplan_in866.h index dad10ca..04dae75 100644 --- a/src/lmic/lmic_bandplan_in866.h +++ b/src/lmic/lmic_bandplan_in866.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,4 +81,8 @@ void LMICin866_initDefaultChannels(bit_t join); void LMICin866_setRx1Params(void); #define LMICbandplan_setRx1Params() LMICin866_setRx1Params() +#undef LMICbandplan_validDR +bit_t LMICin866_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICin866_validDR(dr) + #endif // _lmic_bandplan_in866_h_ diff --git a/src/lmic/lmic_bandplan_kr920.h b/src/lmic/lmic_bandplan_kr920.h index 2c22f22..49beda0 100644 --- a/src/lmic/lmic_bandplan_kr920.h +++ b/src/lmic/lmic_bandplan_kr920.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -88,4 +88,8 @@ void LMICkr920_setRx1Params(void); void LMICkr920_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(t) LMICkr920_updateTx(t) +#undef LMICbandplan_validDR +bit_t LMICkr920_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICkr920_validDR(dr) + #endif // _lmic_kr920_h_ diff --git a/src/lmic/lmic_bandplan_us915.h b/src/lmic/lmic_bandplan_us915.h index e08a795..2c739c2 100644 --- a/src/lmic/lmic_bandplan_us915.h +++ b/src/lmic/lmic_bandplan_us915.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,4 +66,8 @@ void LMICus915_setRx1Params(void); void LMICus915_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(txbeg) +#undef LMICbandplan_validDR +bit_t LMICus915_validDR(dr_t dr); +#define LMICbandplan_validDR(dr) LMICus915_validDR(dr) + #endif // _lmic_bandplan_us915_h_ diff --git a/src/lmic/lmic_channelshuffle.c b/src/lmic/lmic_channelshuffle.c new file mode 100644 index 0000000..252bbee --- /dev/null +++ b/src/lmic/lmic_channelshuffle.c @@ -0,0 +1,217 @@ +/* + +Module: lmic_channelshuffle.c + +Function: + Channel scheduling without replacement. + +Copyright and License: + This file copyright (C) 2021 by + + MCCI Corporation + 3520 Krums Corners Road + Ithaca, NY 14850 + + See accompanying LICENSE file for copyright and license information. + +Author: + Terry Moore, MCCI Corporation April 2021 + +*/ + +#include "lmic.h" +#include + +/****************************************************************************\ +| +| Manifest constants and local declarations. +| +\****************************************************************************/ + +static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries); +static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum); + +/****************************************************************************\ +| +| Read-only data. +| +\****************************************************************************/ + +/****************************************************************************\ +| +| Variables. +| +\****************************************************************************/ + +/* + +Name: LMIC_findNextChannel() + +Function: + Scan a shuffle mask, and select a channel (without replacement). + +Definition: + int LMIC_findNextChannel( + uint16_t *pShuffleMask, + const uint16_t *pEnableMask, + uint16_t nEntries, + int lastChannel + ); + +Description: + pShuffleMask and pEnableMask are bit vectors. Channels correspond to + bits in little-endian order; entry [0] has channels 0 through 15, entry + [1] channels 16 through 31, and so forth. nEntries specifies the number + of entries in the mask vectors. The enable mask is 1 for a given channel + if that channel is eligible for selection, 0 otherwise. + + This routine selects channels from the shuffle mask until all entries + are exhausted; it then refreshes the shuffle mask from the enable mask. + + If it refreshes the channel mask, lastChannel is taken as a channel number + that is to be avoided in the next selection. (This is to avoid back-to-back + use of a channel across a refresh boundary.) Otherwise lastChannel is + ignored. This avoidance can be suppresed by setting lastChannel to -1. + If only one channel is enabled, lastChannel is also ignored. If lastChannel + is actually disabled, lastChannel is also ignored. + +Returns: + A channel number, in 0 .. nEntries-1, or -1 if the enable mask is + identically zero. + +Notes: + This routine is somewhat optimized for AVR processors, which don't have + multi-bit shifts. + +*/ + +int LMIC_findNextChannel( + uint16_t *pShuffleMask, + const uint16_t *pEnableMask, + uint16_t nEntries, + int lastChannel +) { + unsigned nSet16; + uint16_t saveLastChannelVal; + + // in case someone has changed the enable mask, update + // the shuffle mask so there are no disable bits set. + for (unsigned i = 0; i < nEntries; ++i) { + pShuffleMask[i] &= pEnableMask[i]; + } + + // count the set bits in the shuffle mask (with a factor of 16 for speed) + nSet16 = sidewaysSum16(pShuffleMask, nEntries); + + // if zero, copy the enable mask to the shuffle mask, and recount + if (nSet16 == 0) { + memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask)); + nSet16 = sidewaysSum16(pShuffleMask, nEntries); + } else { + // don't try to skip the last channel becuase it can't be chosen. + lastChannel = -1; + } + + // if still zero, return -1. + if (nSet16 == 0) { + return -1; + } + + // if we have to skip a channel, and we have more than one choice, turn off + // the last channel bit. Post condition: if we really clered a bit, + // saveLastChannelVal will be non-zero. + saveLastChannelVal = 0; + if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= nEntries * 16) { + uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF)); + + saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask; + pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask; + + // if we cleared a bit, reduce the count. + if (saveLastChannelVal > 0) + nSet16 -= 16; + } + + if (saveLastChannelVal == 0) { + // We didn't eliminate a channel, so we don't have to worry. + lastChannel = -1; + } + + // get a random number + unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4); + + // choose a bit based on set bit + unsigned channel = findNthSetBit(pShuffleMask, choice); + pShuffleMask[channel / 16] ^= (1 << (channel & 0xF)); + + // handle channel skip + if (lastChannel >= 0) { + pShuffleMask[lastChannel >> 4] |= saveLastChannelVal; + } + return channel; +} + +static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) { + unsigned result; + + result = 0; + for (; nEntries > 0; --nEntries, ++pMask) + { + uint16_t v = *pMask; + + // the following is an adaptation of Knuth 7.1.3 (62). To avoid + // lots of shifts (slow on AVR, and code intensive) and table lookups, + // we sum popc * 16, then divide by 16. + + // sum adjacent bits, making a series of 2-bit sums + v = v - ((v >> 1) & 0x5555u); + v = (v & 0x3333u) + ((v >> 2) & 0x3333u); + // this assumes multiplies are essentialy free; + v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u); + // Accumulate result, but note it's times 16. + // AVR compiler should optimize the x8 shift. + result += (v & 0xFF) + (v >> 8); + } + + // + return result; +} + +static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) { + unsigned result; + result = 0; + bitnum = bitnum * 16; + for (;; result += 16) { + uint16_t m = *pMask++; + if (m == 0) + continue; + uint16_t v = m - ((m >> 1) & 0x5555u); + v = (v & 0x3333u) + ((v >> 2) & 0x3333u); + // this assumes multiplies are essentialy free; + v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u); + // Accumulate result, but note it's times 16. + // AVR compiler should optimize the x8 shift. + v = (v & 0xFF) + (v >> 8); + if (v <= bitnum) + bitnum -= v; + else { + // the selected bit is in this word. We need to count. + while (bitnum > 0) { + m &= m - 1; + bitnum -= 16; + } + // now the lsb of m is our choice. + // get a mask, then use Knuth 7.1.3 (59) to find the + // bit number. + m &= -m; + result += ((m & 0x5555u) ? 0 : 1) + + ((m & 0x3333u) ? 0 : 2) + + ((m & 0x0F0Fu) ? 0 : 4) + + ((m & 0x00FFu) ? 0 : 8) + ; + break; + } + } + + return result; +} \ No newline at end of file diff --git a/src/lmic/lmic_compliance.c b/src/lmic/lmic_compliance.c index d1356b9..8d767b2 100644 --- a/src/lmic/lmic_compliance.c +++ b/src/lmic/lmic_compliance.c @@ -284,7 +284,7 @@ static void evMessage( break; } case LORAWAN_COMPLIANCE_CMD_LINK: { - // not clear what this request does. + // we are required to initiate a Link break; } case LORAWAN_COMPLIANCE_CMD_JOIN: { diff --git a/src/lmic/lmic_config_preconditions.h b/src/lmic/lmic_config_preconditions.h index 4eca675..12b6c67 100644 --- a/src/lmic/lmic_config_preconditions.h +++ b/src/lmic/lmic_config_preconditions.h @@ -118,30 +118,6 @@ Revision history: (1 << LMIC_REGION_in866) | \ 0) -// -// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866 -// More will be added in the the future. So at this point we create CFG_region with -// following values. These are in order of the sections in the manual. Not all of the -// below are supported yet. -// -// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in -// the below. -// -// TODO(tmm@mcci.com) consider moving this block to a central file as it's not -// user-editable. -// -# define CFG_LMIC_REGION_MASK \ - ((defined(CFG_eu868) << LMIC_REGION_eu868) | \ - (defined(CFG_us915) << LMIC_REGION_us915) | \ - (defined(CFG_cn783) << LMIC_REGION_cn783) | \ - (defined(CFG_eu433) << LMIC_REGION_eu433) | \ - (defined(CFG_au915) << LMIC_REGION_au915) | \ - (defined(CFG_cn490) << LMIC_REGION_cn490) | \ - (defined(CFG_as923) << LMIC_REGION_as923) | \ - (defined(CFG_kr920) << LMIC_REGION_kr920) | \ - (defined(CFG_in866) << LMIC_REGION_in866) | \ - 0) - // the selected region. // TODO(tmm@mcci.com) consider moving this block to a central file as it's not // user-editable. @@ -171,11 +147,94 @@ Revision history: # define CFG_region 0 #endif -// a bitmask of EU-like regions -- these are regions which have up to 16 -// channels indidually programmable via downloink. -// -// TODO(tmm@mcci.com) consider moving this block to a central file as it's not -// user-editable. +// LMIC_CFG_*_ENA -- either 1 or 0 based on whether that region +// is enabled. Note: these must be after the code that special-cases +// CFG_as923jp. +#if defined(CFG_eu868) +# define LMIC_CFG_eu868_ENA 1 +#else +# define LMIC_CFG_eu868_ENA 0 +#endif + +#if defined(CFG_us915) +# define LMIC_CFG_us915_ENA 1 +#else +# define LMIC_CFG_us915_ENA 0 +#endif + +#if defined(CFG_cn783) +# define LMIC_CFG_cn783_ENA 1 +#else +# define LMIC_CFG_cn783_ENA 0 +#endif + +#if defined(CFG_eu433) +# define LMIC_CFG_eu433_ENA 1 +#else +# define LMIC_CFG_eu433_ENA 0 +#endif + +#if defined(CFG_au915) +# define LMIC_CFG_au915_ENA 1 +#else +# define LMIC_CFG_au915_ENA 0 +#endif + +#if defined(CFG_cn490) +# define LMIC_CFG_cn490_ENA 1 +#else +# define LMIC_CFG_cn490_ENA 0 +#endif + +#if defined(CFG_as923) +# define LMIC_CFG_as923_ENA 1 +#else +# define LMIC_CFG_as923_ENA 0 +#endif + +#if defined(CFG_kr920) +# define LMIC_CFG_kr920_ENA 1 +#else +# define LMIC_CFG_kr920_ENA 0 +#endif + +#if defined(CFG_in866) +# define LMIC_CFG_in866_ENA 1 +#else +# define LMIC_CFG_in866_ENA 0 +#endif + +/// \brief Bitmask of configured regions +/// +/// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866 +/// More will be added in the the future. So at this point we create CFG_region with +/// following values. These are in order of the sections in the manual. Not all of the +/// below are supported yet. +/// +/// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in +/// the below. +/// +/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not +/// user-editable. +/// +# define CFG_LMIC_REGION_MASK \ + ((LMIC_CFG_eu868_ENA << LMIC_REGION_eu868) | \ + (LMIC_CFG_us915_ENA << LMIC_REGION_us915) | \ + (LMIC_CFG_cn783_ENA << LMIC_REGION_cn783) | \ + (LMIC_CFG_eu433_ENA << LMIC_REGION_eu433) | \ + (LMIC_CFG_au915_ENA << LMIC_REGION_au915) | \ + (LMIC_CFG_cn490_ENA << LMIC_REGION_cn490) | \ + (LMIC_CFG_as923_ENA << LMIC_REGION_as923) | \ + (LMIC_CFG_kr920_ENA << LMIC_REGION_kr920) | \ + (LMIC_CFG_in866_ENA << LMIC_REGION_in866) | \ + 0) + +/// \brief a bitmask of EU-like regions +/// +/// EU-like regions have up to 16 channels individually programmable via downlink. +/// +/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not +/// user-editable. #define CFG_LMIC_EU_like_MASK ( \ (1 << LMIC_REGION_eu868) | \ /* (1 << LMIC_REGION_us915) | */ \ @@ -188,12 +247,14 @@ Revision history: (1 << LMIC_REGION_in866) | \ 0) -// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels -// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but -// subsets of channels can be selected via masks. -// -// TODO(tmm@mcci.com) consider moving this block to a central file as it's not -// user-editable. +/// \brief bitmask of` US-like regions +/// +/// US-like regions have 64 fixed 125-kHz channels overlaid by 8 500-kHz +/// channels. The channel frequencies can't be changed, but +/// subsets of channels can be selected via masks. +/// +/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not +/// user-editable. #define CFG_LMIC_US_like_MASK ( \ /* (1 << LMIC_REGION_eu868) | */ \ (1 << LMIC_REGION_us915) | \ @@ -211,16 +272,19 @@ Revision history: // TODO(tmm@mcci.com) consider moving this block to a central file as it's not // user-editable. // + +/// \brief true if configured region is EU-like, false otherwise. #define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK)) +/// \brief true if configured region is US-like, false otherwise. #define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK)) // -// The supported LMIC LoRaWAAN spec versions. These need to be numerically ordered, +// The supported LMIC LoRaWAN spec versions. These need to be numerically ordered, // so that we can (for example) compare // // LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3. // -#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u -#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u +#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u /// Refers to LoRaWAN spec 1.0.2 +#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u /// Refers to LoRaWAN spec 1.0.3 #endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */ diff --git a/src/lmic/lmic_eu868.c b/src/lmic/lmic_eu868.c index f8d51d1..c8653cb 100644 --- a/src/lmic/lmic_eu868.c +++ b/src/lmic/lmic_eu868.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; +bit_t +LMICeu868_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + static CONST_TABLE(u1_t, maxFrameLens)[] = { 59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5 }; @@ -140,17 +148,28 @@ static CONST_TABLE(u4_t, bandAssignments)[] = { 865000000 /* .. 868400000 */ | BAND_CENTI, }; +/// +/// \brief query number of default channels. +/// +u1_t LMIC_queryNumDefaultChannels() { + return NUM_DEFAULT_CHANNELS; +} + +/// +/// \brief LMIC_setupChannel for EU 868 +/// +/// \note according to LoRaWAN 1.0.3 section 5.6, "the acceptable range +/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS. +/// This routine is used internally for MAC commands, so we enforce +/// this for the extenal API as well. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { // zero the band bits in freq, just in case. freq &= ~3; if (chidx < NUM_DEFAULT_CHANNELS) { - // can't disable a default channel. - if (freq == 0) - return 0; - // can't change a default channel. - else if (freq != (LMIC.channelFreq[chidx] & ~3)) - return 0; + // can't do anything to a default channel. + return 0; } bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) @@ -204,32 +223,111 @@ ostime_t LMICeu868_nextJoinTime(ostime_t time) { return time; } +/// +/// \brief change the TX channel given the desired tx time. +/// +/// \param [in] now is the time at which we want to transmit. In fact, it's always +/// the current time. +/// +/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the +/// selected channel. +/// +/// \details +/// We scan all the channels, creating a mask of all enabled channels that are +/// feasible at the earliest possible time. We then randomly choose one from +/// that, updating the shuffle mask. +/// +/// One sublety is that we have to cope with an artifact of the shuffler. +/// It will zero out bits for candidates that are real candidates, but +/// not in the time window, and not consider them as early as it should. +/// So we keep a mask of all feasible channels, and make sure that they +/// remain set in the shuffle mask if appropriate. +/// ostime_t LMICeu868_nextTx(ostime_t now) { - u1_t bmap = 0xF; - do { - ostime_t mintime = now + /*8h*/sec2osticks(28800); - u1_t band = 0; - for (u1_t bi = 0; bi<4; bi++) { - if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0) - mintime = LMIC.bands[band = bi].avail; - } - // Find next channel in given band - u1_t chnl = LMIC.bands[band].lastchnl; - for (u1_t ci = 0; ci= MAX_CHANNELS) - chnl -= MAX_CHANNELS; - if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled - (LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && - band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band - LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; - return mintime; - } - } - if ((bmap &= ~(1 << band)) == 0) { - // No feasible channel found! - return mintime; - } - } while (1); + ostime_t mintime = now + /*8h*/sec2osticks(28800); + u2_t availMap; + u2_t feasibleMap; + u1_t bandMap; + + // set mintime to the earliest time of all enabled channels + // (can't just look at bands); and for a given channel, we + // can't tell if we're ready till we've checked all possible + // avail times. + bandMap = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + u2_t chnlBit = 1 << chnl; + + // none at any higher numbers? + if (LMIC.channelMap < chnlBit) + break; + + // not enabled? + if ((LMIC.channelMap & chnlBit) == 0) + continue; + + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + + u1_t const band = LMIC.channelFreq[chnl] & 0x3; + u1_t const thisBandBit = 1 << band; + // already considered? + if ((bandMap & thisBandBit) != 0) + continue; + + // consider this band. + bandMap |= thisBandBit; + + // enabled, not considered, feasible: adjust the min time. + if ((s4_t)(mintime - LMIC.bands[band].avail) > 0) + mintime = LMIC.bands[band].avail; + } + + // make a mask of candidates available for use + availMap = 0; + feasibleMap = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + u2_t chnlBit = 1 << chnl; + + // none at any higher numbers? + if (LMIC.channelMap < chnlBit) + break; + + // not enabled? + if ((LMIC.channelMap & chnlBit) == 0) + continue; + + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + + // This channel is feasible. But might not be available. + feasibleMap |= chnlBit; + + // not available yet? + u1_t const band = LMIC.channelFreq[chnl] & 0x3; + if ((s4_t)(LMIC.bands[band].avail - mintime) > 0) + continue; + + // ok: this is a candidate. + availMap |= chnlBit; + } + + // find the next available chennel. + u2_t saveShuffleMap = LMIC.channelShuffleMap; + int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl); + + // restore bits in the shuffleMap that were on, but might have reset + // if availMap was used to refresh shuffleMap. These are channels that + // are feasble but not yet candidates due to band saturation + LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap; + + if (candidateCh >= 0) { + // update the channel; otherwise we'll just use the + // most recent one. + LMIC.txChnl = candidateCh; + } + return mintime; } diff --git a/src/lmic/lmic_eu_like.c b/src/lmic/lmic_eu_like.c index 46694e3..733ff3c 100644 --- a/src/lmic/lmic_eu_like.c +++ b/src/lmic/lmic_eu_like.c @@ -128,7 +128,9 @@ void LMICeulike_initJoinLoop(uint8_t nDefaultChannels, s1_t adrTxPow) { #if CFG_TxContinuousMode LMIC.txChnl = 0 #else - LMIC.txChnl = os_getRndU1() % nDefaultChannels; + uint16_t enableMap = (1 << nDefaultChannels) - 1; + LMIC.channelShuffleMap = enableMap; + LMIC.txChnl = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, -1); #endif LMIC.adrTxPow = adrTxPow; // TODO(tmm@mcci.com) don't use EU directly, use a table. That @@ -166,12 +168,11 @@ void LMICeulike_updateTx(ostime_t txbeg) { // ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) { u1_t failed = 0; + u2_t enableMap = (1 << nDefaultChannels) - 1; // Try each default channel with same DR // If all fail try next lower datarate - if (++LMIC.txChnl == /* NUM_DEFAULT_CHANNELS */ nDefaultChannels) - LMIC.txChnl = 0; - if ((++LMIC.txCnt % nDefaultChannels) == 0) { + if (LMIC.channelShuffleMap == 0) { // Lower DR every nth try (having all default channels with same DR) // // TODO(tmm@mcci.com) add new DR_REGION_JOIN_MIN instead of LORAWAN_DR0; @@ -179,7 +180,6 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) { // the failed flag here. This will cause the outer caller to take the // appropriate join path. Or add new LMICeulike_GetLowestJoinDR() // - // TODO(tmm@mcci.com) - see above; please remove regional dependency from this file. #if CFG_region == LMIC_REGION_as923 // in the join of AS923 v1.1 or older, only DR2 is used. @@ -187,15 +187,22 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) { LMIC.datarate = AS923_DR_SF10; failed = 1; #else - if (LMIC.datarate == LORAWAN_DR0) + if (LMIC.datarate == LORAWAN_DR0) { failed = 1; // we have tried all DR - signal EV_JOIN_FAILED - else { + } else { LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate)); } #endif } - // Clear NEXTCHNL because join state engine controls channel hopping + + // find new channel, avoiding repeats. + int newCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, LMIC.txChnl); + if (newCh >= 0) + LMIC.txChnl = newCh; + + // Clear OP_NEXTCHNL because join state engine controls channel hopping LMIC.opmode &= ~OP_NEXTCHNL; + // Move txend to randomize synchronized concurrent joins. // Duty cycle is based on txend. ostime_t const time = LMICbandplan_nextJoinTime(os_getTime()); @@ -220,6 +227,27 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) { } #endif // !DISABLE_JOIN +#if !defined(DISABLE_JOIN) +void LMICeulike_processJoinAcceptCFList(void) { + if ( LMICbandplan_hasJoinCFlist() && + LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_FREQUENCIES) { + u1_t dlen; + u1_t nDefault = LMIC_queryNumDefaultChannels(); + + dlen = OFF_CFLIST; + for( u1_t chidx = nDefault; chidx < nDefault + 5; chidx++, dlen+=3 ) { + u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]); + if( freq ) { + LMIC_setupChannel(chidx, freq, 0, -1); +#if LMIC_DEBUG_LEVEL > 1 + LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq); +#endif + } + } + } +} +#endif // !DISABLE_JOIN + void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) { os_copyMem( pStateBuffer->channelFreq, diff --git a/src/lmic/lmic_eu_like.h b/src/lmic/lmic_eu_like.h index c9bbeac..729af64 100644 --- a/src/lmic/lmic_eu_like.h +++ b/src/lmic/lmic_eu_like.h @@ -66,6 +66,11 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 }; // there's a CFList on joins for EU-like plans #define LMICbandplan_hasJoinCFlist() (1) +/// \brief process CFLists from JoinAccept for EU-like regions +void LMICeulike_processJoinAcceptCFList(void); +/// \brief by default, EU-like plans use LMICeulike_processJoinAcceptCFList +#define LMICbandplan_processJoinAcceptCFList LMICeulike_processJoinAcceptCFList + #define LMICbandplan_advanceBeaconChannel() \ do { /* nothing */ } while (0) diff --git a/src/lmic/lmic_in866.c b/src/lmic/lmic_in866.c index 15916a1..3cb066e 100644 --- a/src/lmic/lmic_in866.c +++ b/src/lmic/lmic_in866.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; +bit_t +LMICin866_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + static CONST_TABLE(u1_t, maxFrameLens)[] = { 59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5 }; @@ -134,17 +142,27 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { return 1; } +/// +/// \brief query number of default channels. +/// +u1_t LMIC_queryNumDefaultChannels() { + return NUM_DEFAULT_CHANNELS; +} + +/// +/// \brief LMIC_setupChannel for IN region +/// +/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range +/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS. +/// This routine is used internally for MAC commands, so we enforce +/// this for the extenal API as well. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { // zero the band bits in freq, just in case. freq &= ~3; if (chidx < NUM_DEFAULT_CHANNELS) { - // can't disable a default channel. - if (freq == 0) - return 0; - // can't change a default channel. - else if (freq != (LMIC.channelFreq[chidx] & ~3)) - return 0; + return 0; } bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) @@ -173,28 +191,44 @@ u4_t LMICin866_convFreq(xref2cu1_t ptr) { return freq; } -// return the next time, but also do channel hopping here -// since there's no duty cycle limitation, and no dwell limitation, -// we simply loop through the channels sequentially. +/// +/// \brief change the TX channel given the desired tx time. +/// +/// \param [in] now is the time at which we want to transmit. In fact, it's always +/// the current time. +/// +/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the +/// selected channel. +/// +/// \details +/// We scan all the bands, creating a mask of all enabled channels that are +/// feasible at the earliest possible time. We then randomly choose one from +/// that, updating the shuffle mask. +/// +/// Since there's no duty cycle limitation, and no dwell limitation, +/// we just choose a channel from the shuffle and return the current time. +/// ostime_t LMICin866_nextTx(ostime_t now) { - const u1_t band = BAND_MILLI; + uint16_t availmask; - for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) { - // Find next channel in given band - u1_t chnl = LMIC.bands[band].lastchnl; - for (u1_t ci = 0; ci= MAX_CHANNELS) - chnl -= MAX_CHANNELS; - if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled - (LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && - band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band - LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; - return now; - } - } + // scan all the enabled channels and make a mask of candidates + availmask = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + // not enabled? + if ((LMIC.channelMap & (1 << chnl)) == 0) + continue; + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + availmask |= 1 << chnl; } - // no enabled channel found! just use the last channel. + // now: calculate the mask + int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl); + if (candidateCh >= 0) { + // update the channel. + LMIC.txChnl = candidateCh; + } return now; } diff --git a/src/lmic/lmic_kr920.c b/src/lmic/lmic_kr920.c index 9ea1dac..e295516 100644 --- a/src/lmic/lmic_kr920.c +++ b/src/lmic/lmic_kr920.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,6 +47,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS, // [6] }; +bit_t +LMICkr920_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + static CONST_TABLE(u1_t, maxFrameLens)[] = { 59+5, 59+5, 59+5, 123+5, 250+5, 250+5 }; @@ -145,17 +153,28 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { return 1; } +/// +/// \brief query number of default channels. +/// +u1_t LMIC_queryNumDefaultChannels() { + return NUM_DEFAULT_CHANNELS; +} + +/// +/// \brief LMIC_setupChannel for KR920 +/// +/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range +/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS. +/// This routine is used internally for MAC commands, so we enforce +/// this for the extenal API as well. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { // zero the band bits in freq, just in case. freq &= ~3; if (chidx < NUM_DEFAULT_CHANNELS) { - // can't disable a default channel. - if (freq == 0) - return 0; // can't change a default channel. - else if (freq != (LMIC.channelFreq[chidx] & ~3)) - return 0; + return 0; } bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) @@ -184,28 +203,44 @@ u4_t LMICkr920_convFreq(xref2cu1_t ptr) { return freq; } -// return the next time, but also do channel hopping here -// since there's no duty cycle limitation, and no dwell limitation, -// we simply loop through the channels sequentially. +/// +/// \brief change the TX channel given the desired tx time. +/// +/// \param [in] now is the time at which we want to transmit. In fact, it's always +/// the current time. +/// +/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the +/// selected channel. +/// +/// \details +/// We scan all the bands, creating a mask of all enabled channels that are +/// feasible at the earliest possible time. We then randomly choose one from +/// that, updating the shuffle mask. +/// +/// Since there's no duty cycle limitation, and no dwell limitation, +/// we just choose a channel from the shuffle and return the current time. +/// ostime_t LMICkr920_nextTx(ostime_t now) { - const u1_t band = BAND_MILLI; + uint16_t availmask; - for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) { - // Find next channel in given band - u1_t chnl = LMIC.bands[band].lastchnl; - for (u1_t ci = 0; ci= MAX_CHANNELS) - chnl -= MAX_CHANNELS; - if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled - (LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && - band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band - LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; - return now; - } - } + // scan all the enabled channels and make a mask of candidates + availmask = 0; + for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) { + // not enabled? + if ((LMIC.channelMap & (1 << chnl)) == 0) + continue; + // not feasible? + if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0) + continue; + availmask |= 1 << chnl; } - // no enabled channel found! just use the last channel. + // now: calculate the mask + int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl); + if (candidateCh >= 0) { + // update the channel. + LMIC.txChnl = candidateCh; + } return now; } diff --git a/src/lmic/lmic_us915.c b/src/lmic/lmic_us915.c index 0d84ac1..0cbf852 100644 --- a/src/lmic/lmic_us915.c +++ b/src/lmic/lmic_us915.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017, 2019 MCCI Corporation. +* Copyright (c) 2017, 2019-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS // [14] }; +bit_t +LMICus915_validDR(dr_t dr) { + // use subtract here to avoid overflow + if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2) + return 0; + return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; +} + static CONST_TABLE(u1_t, maxFrameLens)[] = { 19+5, 61+5, 133+5, 250+5, 250+5, 0, 0,0, 61+5, 133+5, 250+5, 250+5, 250+5, 250+5 @@ -99,21 +107,34 @@ u4_t LMICus915_convFreq(xref2cu1_t ptr) { return freq; } +/// +/// \brief query number of default channels. +/// +/// For US, we have no programmable channels; all channels +/// are fixed. Return the total channel count. +/// +u1_t LMIC_queryNumDefaultChannels() { + return 64 + 8; +} + +/// +/// \brief LMIC_setupChannel for US915 +/// +/// \note there are no progammable channels for US915, so this API +/// always returns FALSE. +/// bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { + LMIC_API_PARAMETER(chidx); + LMIC_API_PARAMETER(freq); + LMIC_API_PARAMETER(drmap); LMIC_API_PARAMETER(band); - if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS) - return 0; // channels 0..71 are hardwired - LMIC.xchFreq[chidx - 72] = freq; - // TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const - LMIC.xchDrMap[chidx - 72] = drmap == 0 ? DR_RANGE_MAP(US915_DR_SF10, US915_DR_SF8C) : drmap; - LMIC.channelMap[chidx >> 4] |= (1 << (chidx & 0xF)); - return 1; + return 0; // channels 0..71 are hardwired } bit_t LMIC_disableChannel(u1_t channel) { bit_t result = 0; - if (channel < 72 + MAX_XCHANNELS) { + if (channel < 72) { if (ENABLED_CHANNEL(channel)) { result = 1; if (IS_CHANNEL_125khz(channel)) @@ -128,7 +149,7 @@ bit_t LMIC_disableChannel(u1_t channel) { bit_t LMIC_enableChannel(u1_t channel) { bit_t result = 0; - if (channel < 72 + MAX_XCHANNELS) { + if (channel < 72) { if (!ENABLED_CHANNEL(channel)) { result = 1; if (IS_CHANNEL_125khz(channel)) @@ -197,13 +218,7 @@ void LMICus915_updateTx(ostime_t txbeg) { } else { // at 500kHz bandwidth, we're allowed more power. LMIC.txpow = 26; - if (chnl < 64 + 8) { - LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP; - } - else { - ASSERT(chnl < 64 + 8 + MAX_XCHANNELS); - LMIC.freq = LMIC.xchFreq[chnl - 72]; - } + LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP; } // Update global duty cycle stats diff --git a/src/lmic/lmic_us_like.c b/src/lmic/lmic_us_like.c index d1383bd..31e4cc1 100644 --- a/src/lmic/lmic_us_like.c +++ b/src/lmic/lmic_us_like.c @@ -36,36 +36,34 @@ # error "LMICuslike_getFirst500kHzDR() not defined by bandplan" #endif -static void setNextChannel(uint start, uint end, uint count) { +/// +/// \brief set LMIC.txChan to the next selected channel. +/// +/// \param [in] start first channel number +/// \param [in] end one past the last channel number +/// +/// \details +/// We set up a call to LMIC_findNextChannel using the channelShuffleMap and +/// the channelEnableMap. We subset these based on start and end. \p start must +/// be a multiple of 16. +/// +static void setNextChannel(uint16_t start, uint16_t end, uint16_t count) { ASSERT(count>0); ASSERT(start> 4; + uint16_t const mapEntries = (end - start + 15) >> 4; - // Last used channel is in range. It is not a candidate, per spec. - uint lastTxChan = LMIC.txChnl; - if (start <= lastTxChan && lastTxChan= 0) + LMIC.txChnl = candidate; } @@ -87,8 +85,11 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) { for (u1_t i = 0; i<4; i++) LMIC.channelMap[i] = 0xFFFF; LMIC.channelMap[4] = 0x00FF; + os_clearMem(LMIC.channelShuffleMap, sizeof(LMIC.channelShuffleMap)); LMIC.activeChannels125khz = 64; LMIC.activeChannels500khz = 8; + // choose a random channel. + LMIC.txChnl = 0xFF; } // verify that a given setting is permitted @@ -230,11 +231,10 @@ bit_t LMICuslike_isDataRateFeasible(dr_t dr) { #if !defined(DISABLE_JOIN) void LMICuslike_initJoinLoop(void) { // set an initial condition so that setNextChannel()'s preconds are met - LMIC.txChnl = 0; + LMIC.txChnl = 0xFF; // then chose a new channel. This gives us a random first channel for - // the join. Minor nit: if channel 0 is enabled, it will never be used - // as the first join channel. The join logic uses the current txChnl, + // the join. The join logic uses the current txChnl, // then changes after the rx window expires; so we need to set a valid // starting point. setNextChannel(0, 64, LMIC.activeChannels125khz); @@ -277,10 +277,13 @@ ostime_t LMICuslike_nextJoinState(void) { if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) { // assume that 500 kHz equiv of last 125 kHz channel // is also enabled, and use it next. + LMIC.txChnl_125kHz = LMIC.txChnl; LMIC.txChnl = 64 + (LMIC.txChnl >> 3); LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR()); } else { + // restore invariant + LMIC.txChnl = LMIC.txChnl_125kHz; setNextChannel(0, 64, LMIC.activeChannels125khz); // TODO(tmm@mcci.com) parameterize @@ -290,6 +293,7 @@ ostime_t LMICuslike_nextJoinState(void) { } LMICcore_setDrJoin(DRCHG_SET, dr); } + // tell the main loop that we've already selected a channel. LMIC.opmode &= ~OP_NEXTCHNL; // TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized; @@ -311,6 +315,32 @@ ostime_t LMICuslike_nextJoinState(void) { } #endif +#if !defined(DISABLE_JOIN) +void LMICuslike_processJoinAcceptCFList(void) { + if ( LMICbandplan_hasJoinCFlist() && + LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_MASK ) { + u1_t dlen; + + dlen = OFF_CFLIST; + for( u1_t chidx = 0; chidx < 8 * sizeof(LMIC.channelMap); chidx += 16, dlen += 2 ) { + u2_t mask = os_rlsbf2(&LMIC.frame[dlen]); +#if LMIC_DEBUG_LEVEL > 1 + LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel mask, group=%u, mask=%04x\n", os_getTime(), chidx, mask); +#endif + for ( u1_t chnum = chidx; chnum < chidx + 16; ++chnum, mask >>= 1) { + if (chnum >= 72) { + break; + } else if (mask & 1) { + LMIC_enableChannel(chnum); + } else { + LMIC_disableChannel(chnum); + } + } + } + } +} +#endif // !DISABLE_JOIN + void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) { os_copyMem( pStateBuffer->channelMap, diff --git a/src/lmic/lmic_us_like.h b/src/lmic/lmic_us_like.h index 6281236..f3e8bfc 100644 --- a/src/lmic/lmic_us_like.h +++ b/src/lmic/lmic_us_like.h @@ -63,8 +63,14 @@ LMICuslike_isValidBeacon1(const uint8_t *d) { // provide a default LMICbandplan_joinAcceptChannelClear() #define LMICbandplan_joinAcceptChannelClear() do { } while (0) -// no CFList on joins for US-like plans -#define LMICbandplan_hasJoinCFlist() (0) +/// \brief there's a CFList on joins for US-like plans +#define LMICbandplan_hasJoinCFlist() (1) + +/// \brief process CFLists from JoinAccept for EU-like regions +void LMICuslike_processJoinAcceptCFList(void); +/// \brief by default, EU-like plans use LMICuslike_processJoinAcceptCFList +#define LMICbandplan_processJoinAcceptCFList LMICuslike_processJoinAcceptCFList + #define LMICbandplan_advanceBeaconChannel() \ do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0) diff --git a/src/lmic/lorabase.h b/src/lmic/lorabase.h index fc0fd2d..632a8b4 100644 --- a/src/lmic/lorabase.h +++ b/src/lmic/lorabase.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. - * Copyritght (c) 2017 MCCI Corporation. + * Copyright (c) 2017-2021 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -445,6 +445,13 @@ enum { LEN_JA = 17, LEN_JAEXT = 17+16 }; + +enum { + // JoinAccept CFList types + LORAWAN_JoinAccept_CFListType_FREQUENCIES = 0, ///< the CFList contains 5 frequencies + LORAWAN_JoinAccept_CFListType_MASK = 1, ///< the CFList contains channel-mask data +}; + enum { // Data frame format OFF_DAT_HDR = 0, @@ -638,17 +645,8 @@ static inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) { #define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8))) // Two frames with params r1/r2 would interfere on air: same SFx + BWx static inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; } - -extern CONST_TABLE(u1_t, _DR2RPS_CRC)[]; -static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); } -static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); } static inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; } static inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; } -static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate -static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate -static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR -static inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range -static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps // // BEG: Keep in sync with lorabase.hpp