Upgrade to mcci-catena/arduino-lmic 4.0.1-pre

This commit is contained in:
Manuel Bleichenbacher 2021-07-25 14:39:11 +02:00
parent 1913840679
commit 99bab17d4b
27 changed files with 1005 additions and 271 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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": [

View File

@ -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

View File

@ -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;

View File

@ -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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime;
}
}
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)

View File

@ -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);

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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 <string.h>
/****************************************************************************\
|
| 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;
}

View File

@ -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: {

View File

@ -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_ */

View File

@ -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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime;
}
}
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;
}

View File

@ -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,

View File

@ -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)

View File

@ -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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return 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;
}

View File

@ -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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return 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;
}

View File

@ -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

View File

@ -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<end);
ASSERT(count <= (end - start));
// We used to pick a random channel once and then just increment. That is not per spec.
// Now we use a new random number each time, because they are not very expensive.
// Regarding the algo below, we cannot pick a number and scan until we hit an enabled channel.
// That would result in the first enabled channel following a set of disabled ones
// being used more frequently than the other enabled channels.
ASSERT((start & 0xF) == 0);
uint16_t const mapStart = 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<end &&
// Adjust count only if still enabled. Otherwise, no chance of selection.
ENABLED_CHANNEL(lastTxChan)) {
--count;
if (count == 0) {
return; // Only one active channel, so keep using it.
}
}
int candidate = start + LMIC_findNextChannel(
LMIC.channelShuffleMap + mapStart,
LMIC.channelMap + mapStart,
mapEntries,
LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl
);
uint nth = os_getRndU1() % count;
for (u1_t chnl = start; chnl<end; chnl++) {
// Scan for nth enabled channel that is not the last channel used
if (chnl != lastTxChan && ENABLED_CHANNEL(chnl) && (nth--) == 0) {
LMIC.txChnl = chnl;
return;
}
}
// No feasible channel found! Keep old one.
if (candidate >= 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,

View File

@ -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)

View File

@ -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