diff --git a/src/hal/hal_esp32.cpp b/src/hal/hal_esp32.cpp index 901ef9b..a29fa3b 100755 --- a/src/hal/hal_esp32.cpp +++ b/src/hal/hal_esp32.cpp @@ -448,3 +448,7 @@ void hal_failed(const char *file, u2_t line) ESP_LOGE(TAG, "%s:%d", file, line); ASSERT(0); } + +uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency) { + return LMICHAL_radio_tx_power_policy_paboost; +} diff --git a/src/lmic/config.h b/src/lmic/config.h index 253fd0a..c50ad36 100755 --- a/src/lmic/config.h +++ b/src/lmic/config.h @@ -116,22 +116,16 @@ // define these in lmic_project_config.h to disable the corresponding MAC commands. // Class A -//#define DISABLE_MCMD_DCAP_REQ // duty cycle cap -//#define DISABLE_MCMD_DN2P_SET // 2nd DN window param -//#define DISABLE_MCMD_SNCH_REQ // set new channel +//#define DISABLE_MCMD_DutyCycleReq // duty cycle cap +//#define DISABLE_MCMD_RXParamSetupReq // 2nd DN window param +//#define DISABLE_MCMD_NewChannelReq // set new channel +//#define DISABLE_MCMD_DlChannelReq // set downlink channel for RX1 for given uplink channel. +//#define DISABLE_MCMD_RXTimingSetupReq // delay between TX and RX // Class B -//#define DISABLE_MCMD_PING_SET // set ping freq, automatically disabled by DISABLE_PING -//#define DISABLE_MCMD_BCNI_ANS // next beacon start, automatically disabled by DISABLE_BEACON +//#define DISABLE_MCMD_PingSlotChannelReq // set ping freq, automatically disabled by DISABLE_PING +//#define ENABLE_MCMD_BeaconTimingAns // next beacon start, DEPRECATED, normally disabled by DISABLE_BEACON -// In LoRaWAN, a gateway applies I/Q inversion on TX, and nodes do the -// same on RX. This ensures that gateways can talk to nodes and vice -// versa, but gateways will not hear other gateways and nodes will not -// hear other nodes. By defining this macro in lmic_project_config.h, -// this inversion is disabled and this node can hear other nodes. If -// two nodes both have this macro set, they can talk to each other -// (but they can no longer hear gateways). This should probably only -// be used when debugging and/or when talking to the radio directly -// (e.g. like in the "raw" example). +// DEPRECATED(tmm@mcci.com); replaced by LMIC.noRXIQinversion (dynamic). Don't define this. //#define DISABLE_INVERT_IQ_ON_RX // This allows choosing between multiple included AES implementations. @@ -178,4 +172,36 @@ # define LMIC_ENABLE_DeviceTimeReq 0 #endif +// LMIC_ENABLE_user_events +// Enable/disable support for programmable callbacks for events, rx, and tx. +// This is always defined, and non-zero to enable. Default is enabled. +#if !defined(LMIC_ENABLE_user_events) +# define LMIC_ENABLE_user_events 1 +#endif + +// LMIC_ENABLE_onEvent +// Enable/disable support for out-call to user-supplied `onEvent()` function. +// This is always defined, and non-zero to enable. Default is enabled. +#if !defined(LMIC_ENABLE_onEvent) +# define LMIC_ENABLE_onEvent 1 +#endif + +// LMIC_ENABLE_long_messages +// LMIC certification requires that this be enabled. +#if !defined(LMIC_ENABLE_long_messages) +# define LMIC_ENABLE_long_messages 1 /* PARAM */ +#endif + +// LMIC_ENABLE_event_logging +// LMIC debugging for certification tests requires this, because debug prints affect +// timing too dramatically. But normal operation doesn't need this. +#if !defined(LMIC_ENABLE_event_logging) +# define LMIC_ENABLE_event_logging 0 /* PARAM */ +#endif + +// LMIC_LORAWAN_SPEC_VERSION +#if !defined(LMIC_LORAWAN_SPEC_VERSION) +# define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3 +#endif + #endif // _lmic_config_h_ diff --git a/src/lmic/hal.h b/src/lmic/hal.h index 5b610f4..00a6e58 100755 --- a/src/lmic/hal.h +++ b/src/lmic/hal.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. - * Copyright (c) 2016, 2018 MCCI Corporation. + * Copyright (c) 2016, 2018-2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,10 +33,17 @@ # include "oslmic_types.h" #endif +#ifndef _lmic_env_h_ +# include "lmic_env.h" +#endif + #ifdef __cplusplus extern "C"{ #endif +// The type of an optional user-defined failure handler routine +typedef void LMIC_ABI_STD hal_failure_handler_t(const char* const file, const uint16_t line); + /* * initialize hardware (IO, SPI, TIMER, IRQ). * This API is deprecated as it uses the const global lmic_pins, @@ -87,6 +94,11 @@ void hal_disableIRQs (void); */ void hal_enableIRQs (void); +/* + * return CPU interrupt nesting count + */ +uint8_t hal_getIrqLevel (void); + /* * put system and CPU in low-power mode, sleep until interrupt. */ @@ -116,6 +128,12 @@ u1_t hal_checkTimer (u4_t targettime); */ void hal_failed (const char *file, u2_t line); +/* + * set a custom hal failure handler routine. The default behaviour, defined in + * hal_failed(), is to halt by looping infintely. + */ +void hal_set_failure_handler(const hal_failure_handler_t* const); + /* * get the calibration value for radio_rssi */ @@ -129,8 +147,27 @@ s1_t hal_getRssiCal (void); */ ostime_t hal_setModuleActive (bit_t val); +/* find out if we're using Tcxo */ bit_t hal_queryUsingTcxo(void); +/* represent the various radio TX power policy */ +enum { + LMICHAL_radio_tx_power_policy_rfo = 0, + LMICHAL_radio_tx_power_policy_paboost = 1, + LMICHAL_radio_tx_power_policy_20dBm = 2, +}; + +/* + * query the configuration as to the Tx Power Policy + * to be used on this board, given our desires and + * requested power. + */ +uint8_t hal_getTxPowerPolicy( + u1_t inputPolicy, + s1_t requestedPower, + u4_t freq + ); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index a7f253c..e431e8c 100755 --- a/src/lmic/lmic.c +++ b/src/lmic/lmic.c @@ -2,7 +2,7 @@ * Copyright (c) 2014-2016 IBM Corporation. * All rights reserved. * - * Copyright (c) 2016-2018 MCCI Corporation. + * Copyright (c) 2016-2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,14 +38,19 @@ DEFINE_LMIC; - // Fwd decls. +static void reportEventNoUpdate(ev_t); +static void reportEventAndUpdate(ev_t); static void engineUpdate(void); +static bit_t processJoinAccept_badframe(void); +static bit_t processJoinAccept_nojoinframe(void); + #if !defined(DISABLE_BEACONS) static void startScan (void); #endif +// set the txrxFlags, with debugging static inline void initTxrxFlags(const char *func, u1_t mask) { LMIC_DEBUG2_PARAMETER(func); @@ -55,6 +60,7 @@ static inline void initTxrxFlags(const char *func, u1_t mask) { LMIC.txrxFlags = mask; } +// or the txrxFlags, with debugging static inline void orTxrxFlags(const char *func, u1_t mask) { initTxrxFlags(func, LMIC.txrxFlags | mask); } @@ -278,17 +284,6 @@ ostime_t calcAirTime (rps_t rps, u1_t plen) { // ================================================================================ -// Adjust DR for TX retries -// - indexed by retry count -// - return steps to lower DR -static CONST_TABLE(u1_t, DRADJUST)[2+TXCONF_ATTEMPTS] = { - // normal frames - 1st try / no retry - 0, - // confirmed frames - 0,0,1,0,1,0,1,0,0 -}; - - // Table below defines the size of one symbol as // symtime = 256us * 2^T(sf,bw) // 256us is called one symunit. @@ -311,11 +306,11 @@ static ostime_t calcRxWindow (u1_t secs, dr_t dr) { rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; } - u1_t rxsyms = MINRX_SYMS; + u1_t rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB; err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; - LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr)); + LMIC.rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB + (err / dr2hsym(dr)); - return (rxsyms-PAMBL_SYMS) * dr2hsym(dr) + rxoff; + return (rxsyms-LMICbandplan_PAMBL_SYMS) * dr2hsym(dr) + rxoff; } @@ -328,8 +323,8 @@ static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) { LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF; } ostime_t hsym = dr2hsym(DR_BCN); - LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; - LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym; + LMIC.bcnRxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB + ms2osticksCeil(ms) / hsym; + LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-LMICbandplan_PAMBL_SYMS) * hsym; } #endif // !DISABLE_BEACONS @@ -382,9 +377,10 @@ ostime_t LMICcore_rndDelay (u1_t secSpan) { return delay; } - +// delay reftime ticks, plus a random interval in [0..secSpan). static void txDelay (ostime_t reftime, u1_t secSpan) { - reftime += LMICcore_rndDelay(secSpan); + if (secSpan != 0) + reftime += LMICcore_rndDelay(secSpan); if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) { LMIC.globalDutyAvail = reftime; LMIC.opmode |= OP_RNDTX; @@ -406,7 +402,9 @@ void LMICcore_setDrJoin (u1_t reason, u1_t dr) { } -static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { +static bit_t setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { + bit_t result = 0; + LMIC_EV_PARAMETER(reason); EV(drChange, INFO, (e_.reason = reason, @@ -416,13 +414,17 @@ static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { e_.prevdr = LMIC.datarate|DR_PAGE, e_.prevtxpow = LMIC.adrTxPow)); - if( pow != KEEP_TXPOW ) + if( pow != KEEP_TXPOW && pow != LMIC.adrTxPow ) { LMIC.adrTxPow = pow; + result = 1; + } if( LMIC.datarate != dr ) { LMIC.datarate = dr; DO_DEVDB(LMIC.datarate,datarate); LMIC.opmode |= OP_NEXTCHNL; + result = 1; } + return result; } @@ -450,48 +452,153 @@ static void runEngineUpdate (xref2osjob_t osjob) { engineUpdate(); } - -static void reportEvent (ev_t ev) { - EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, - e_.eui = MAIN::CDEV->getEui(), - e_.info = ev)); - ON_LMIC_EVENT(ev); +static void reportEventAndUpdate(ev_t ev) { + reportEventNoUpdate(ev); engineUpdate(); } +static void reportEventNoUpdate (ev_t ev) { + uint32_t const evSet = UINT32_C(1) << ev; + EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, + e_.eui = MAIN::CDEV->getEui(), + e_.info = ev)); +#if LMIC_ENABLE_onEvent + void (*pOnEvent)(ev_t) = onEvent; + + // rxstart is critical timing; legacy onEvent handlers + // don't comprehend this; so don't report. + if (pOnEvent != NULL && (evSet & (UINT32_C(1)<> 8; // read as signed 24-bit LMIC.bcninfo.lon = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto LMIC.bcninfo.info = d[OFF_BCN_INFO]; LMIC.bcninfo.flags |= BCN_FULL; - return 2; + return LMIC_BEACON_ERROR_SUCCESS_FULL; } #endif // !DISABLE_BEACONS +// put a mac response to the current output buffer. Limit according to kind of +// mac data (piggyback vs port 0) +static bit_t put_mac_uplink_byte(uint8_t b) { + if (LMIC.pendMacPiggyback) { + // put in pendMacData + if (LMIC.pendMacLen < sizeof(LMIC.pendMacData)) { + LMIC.pendMacData[LMIC.pendMacLen++] = b; + return 1; + } else { + return 0; + } + } else { + // put in pendTxData + if (LMIC.pendMacLen < sizeof(LMIC.pendTxData)) { + LMIC.pendTxData[LMIC.pendMacLen++] = b; + return 1; + } else { + return 0; + } + } +} + +static bit_t put_mac_uplink_byte2(uint8_t b1, uint8_t b2) { + u1_t outindex = LMIC.pendMacLen; + + if (put_mac_uplink_byte(b1) && put_mac_uplink_byte(b2)) { + return 1; + } else { + LMIC.pendMacLen = outindex; + return 0; + } +} + +static bit_t put_mac_uplink_byte3(u1_t b1, u1_t b2, u1_t b3) { + u1_t outindex = LMIC.pendMacLen; + + if (put_mac_uplink_byte(b1) && put_mac_uplink_byte(b2) && put_mac_uplink_byte(b3)) { + return 1; + } else { + LMIC.pendMacLen = outindex; + return 0; + } +} + +static CONST_TABLE(u1_t, macCmdSize)[] = { + /* 2: LinkCheckAns */ 3, + /* 3: LinkADRReq */ 5, + /* 4: DutyCycleReq */ 2, + /* 5: RXParamSetupReq */ 5, + /* 6: DevStatusReq */ 1, + /* 7: NewChannelReq */ 6, + /* 8: RXTimingSetupReq */ 2, + /* 9: TxParamSetupReq */ 2, + /* 0x0A: DlChannelReq */ 5, + /* B, C: RFU */ 0, 0, + /* 0x0D: DeviceTimeAns */ 6, + /* 0x0E, 0x0F */ 0, 0, + /* 0x10: PingSlotInfoAns */ 1, + /* 0x11: PingSlotChannelReq */ 5, + /* 0x12: BeaconTimingAns */ 4, + /* 0x13: BeaconFreqReq */ 4 +}; + +static u1_t getMacCmdSize(u1_t macCmd) { + if (macCmd < 2) + return 0; + if (macCmd >= LENOF_TABLE(macCmdSize) - 2) + return 0; + return TABLE_GET_U1(macCmdSize, macCmd - 2); +} + +static bit_t +applyAdrRequests( + const uint8_t *opts, + int olen, + u1_t adrAns +) { + lmic_saved_adr_state_t initialState; + int const kAdrReqSize = 5; + int oidx; + u1_t p1 = 0; + u1_t p4 = 0; + bit_t response_fit = 1; + bit_t map_ok = 1; + + LMICbandplan_saveAdrState(&initialState); + + // compute the changes + if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { + for (oidx = 0; oidx < olen; oidx += kAdrReqSize) { + // can we advance? + if (olen - oidx < kAdrReqSize) { + // ignore the malformed one at the end + break; + } + u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels + + p1 = opts[oidx+1]; // txpow + DR, in case last + p4 = opts[oidx+4]; // ChMaskCtl, NbTrans + u1_t chpage = p4 & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page + + map_ok = LMICbandplan_mapChannels(chpage, chmap); + LMICOS_logEventUint32("applyAdrRequests: mapChannels", (chpage << 16)|(chmap << 0)); + } + } + + if (! map_ok) { + adrAns &= ~MCMD_LinkADRAns_ChannelACK; + } + + // p1 now has txpow + DR. DR must be feasible. + dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT); + + if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK) && ! LMICbandplan_isDataRateFeasible(dr)) { + adrAns &= ~MCMD_LinkADRAns_DataRateACK; + LMICOS_logEventUint32("applyAdrRequests: final DR not feasible", dr); + } + + if (adrAns != (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { + LMICbandplan_restoreAdrState(&initialState); + } + + // now put all the options + for (oidx = 0; oidx < olen && response_fit; oidx += kAdrReqSize) { + // can we advance? + if (olen - oidx < kAdrReqSize) { + // ignore the malformed one at the end + break; + } + response_fit = put_mac_uplink_byte2(MCMD_LinkADRAns, adrAns); + } + + // all done scanning options + bit_t changes = LMICbandplan_compareAdrState(&initialState); + + // handle the final options + if (adrAns == (MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK)) { + // handle uplink repeat count + u1_t uprpt = p4 & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count + if (LMIC.upRepeat != uprpt) { + LMIC.upRepeat = uprpt; + changes = 1; + } + + LMICOS_logEventUint32("applyAdrRequests: setDrTxPow", (adrAns << 16)|(dr << 8)|(p1 << 0)); + + // handle power changes here, too. + changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); + } + + // Certification doesn't like this, but it makes the device happier with TTN. + // LMIC.adrChanged = changes; // move the ADR FSM up to "time to request" + + return response_fit; +} + +static int +scan_mac_cmds_link_adr( + const uint8_t *opts, + int olen, + bit_t *presponse_fit + ) + { + LMICOS_logEventUint32("scan_mac_cmds_link_adr", olen); + + if (olen == 0) + return 0; + + int oidx = 0; + int const kAdrReqSize = 5; + int lastOidx; + u1_t adrAns = MCMD_LinkADRAns_PowerACK | MCMD_LinkADRAns_DataRateACK | MCMD_LinkADRAns_ChannelACK; + + // process the contiguous slots + for (;;) { + lastOidx = oidx; + + // can we advance? + if (olen - oidx < kAdrReqSize) { + // ignore the malformed one at the end; but fail it. + adrAns = 0; + break; + } + u1_t p1 = opts[oidx+1]; // txpow + DR + u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels + u1_t chpage = opts[oidx+4] & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page + // u1_t uprpt = opts[oidx+4] & MCMD_LinkADRReq_Redundancy_NbTrans_MASK; // up repeat count + dr_t dr = (dr_t)(p1>>MCMD_LinkADRReq_DR_SHIFT); + + if( !LMICbandplan_canMapChannels(chpage, chmap) ) { + adrAns &= ~MCMD_LinkADRAns_ChannelACK; + LMICOS_logEventUint32("scan_mac_cmds_link_adr: failed canMapChannels", (chpage << UINT32_C(16))|(chmap << UINT32_C(0))); + } + + if( !validDR(dr) ) { + adrAns &= ~MCMD_LinkADRAns_DataRateACK; + } + if (pow2dBm(p1) == -128) { + adrAns &= ~MCMD_LinkADRAns_PowerACK; + } + + oidx += kAdrReqSize; + if (opts[oidx] != MCMD_LinkADRReq) + break; + } + + // go back and apply the ADR changes, if any -- use the effective length, + // and process. + *presponse_fit = applyAdrRequests(opts, lastOidx + kAdrReqSize, adrAns); + + return lastOidx; + } + // scan mac commands starting at opts[] for olen, return count of bytes consumed. +// build response in pendMacData[], but limit length as needed; simply chop at last +// response that fits. static int scan_mac_cmds( const uint8_t *opts, - int olen + int olen, + int port ) { int oidx = 0; + uint8_t cmd; + + LMIC.pendMacLen = 0; + if (port == 0) + LMIC.pendMacPiggyback = 0; + else + LMIC.pendMacPiggyback = 1; + while( oidx < olen ) { - switch( opts[oidx] ) { - case MCMD_LCHK_ANS: { + bit_t response_fit; + + response_fit = 1; + cmd = opts[oidx]; + + /* compute length, and exit for illegal commands */ + int const cmdlen = getMacCmdSize(cmd); + if (cmdlen > olen - oidx) { + // "the first unknown command terminates processing" + olen = oidx; + break; + } + + switch( cmd ) { + case MCMD_LinkCheckAns: { + // TODO(tmm@mcci.com) capture these, reliably.. //int gwmargin = opts[oidx+1]; //int ngws = opts[oidx+2]; - oidx += 3; - continue; + break; } - case MCMD_LADR_REQ: { - u1_t p1 = opts[oidx+1]; // txpow + DR - u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels - u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page - u1_t uprpt = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count - oidx += 5; - - // TODO(tmm@mcci.com): LoRaWAN 1.1 requires us to process multiple - // LADR requests, and only update if all pass. So this should check - // ladrAns == 0, and only initialize if so. Need to repeat ACKs, so - // we need to count the number we see. - LMIC.ladrAns = 0x80 | // Include an answer into next frame up - MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; - if( !LMICbandplan_mapChannels(chpage, chmap) ) - LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK; - dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); - if( !validDR(dr) ) { - LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK; - EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = Base::msbf4(&opts[oidx-4]))); - } - // TODO(tmm@mcci.com): see above; this needs to move outside the - // txloop. And we need to have "consistent" answers for the block - // of contiguous commands (whatever that means), and ignore the - // data rate, NbTrans (uprpt) and txPow until the last one. -#if LMIC_DEBUG_LEVEL > 0 - LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": LinkAdrReq: p1:%02x chmap:%04x chpage:%02x uprt:%02x ans:%02x\n", - os_getTime(), p1, chmap, chpage, uprpt, LMIC.ladrAns - ); -#endif /* LMIC_DEBUG_LEVEL */ - - if( (LMIC.ladrAns & 0x7F) == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) { - // Nothing went wrong - use settings - LMIC.upRepeat = uprpt; - setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); - } - LMIC.adrChanged = 1; // Trigger an ACK to NWK - continue; + // from 1.0.3 spec section 5.2: + // For the purpose of configuring the end-device channel mask, the end-device will + // process all contiguous LinkAdrReq messages, in the order present in the downlink message, + // as a single atomic block command. The end-device will accept or reject all Channel Mask + // controls in the contiguous block, and provide consistent Channel Mask ACK status + // indications for each command in the contiguous block in each LinkAdrAns message, + // reflecting the acceptance or rejection of this atomic channel mask setting. + // + // So we need to process all the contigious commands + case MCMD_LinkADRReq: { + // skip over all but the last command. + oidx += scan_mac_cmds_link_adr(opts + oidx, olen - oidx, &response_fit); + break; } - case MCMD_DEVS_REQ: { - LMIC.devsAns = 1; - // LMIC.snr is SNR time 4, convert to real SNR; rounding towards zero. + + case MCMD_DevStatusReq: { + // LMIC.snr is SNR times 4, convert to real SNR; rounding towards zero. const int snr = (LMIC.snr + 2) / 4; // per [1.02] 5.5. the margin is the SNR. LMIC.devAnsMargin = (u1_t)(0b00111111 & (snr <= -32 ? -32 : snr >= 31 ? 31 : snr)); - oidx += 1; - continue; + + response_fit = put_mac_uplink_byte3(MCMD_DevStatusAns, os_getBattLevel(), LMIC.devAnsMargin); + break; } - case MCMD_DN2P_SET: { -#if !defined(DISABLE_MCMD_DN2P_SET) + +#if !defined(DISABLE_MCMD_RXParamSetupReq) + case MCMD_RXParamSetupReq: { dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); u1_t rx1DrOffset = (u1_t)((opts[oidx+1] & 0x70) >> 4); u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); - LMIC.dn2Ans = 0x80; // answer pending + LMIC.dn2Ans = 0xC0; // answer pending, but send this one in order. if( validDR(dr) ) - LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK; + LMIC.dn2Ans |= MCMD_RXParamSetupAns_RX2DataRateACK; if( freq != 0 ) - LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK; + LMIC.dn2Ans |= MCMD_RXParamSetupAns_ChannelACK; if (rx1DrOffset <= 3) - LMIC.dn2Ans |= MCMD_DN2P_ANS_RX1DrOffsetAck; + LMIC.dn2Ans |= MCMD_RXParamSetupAns_RX1DrOffsetAck; - if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK| MCMD_DN2P_ANS_RX1DrOffsetAck) ) { + if( LMIC.dn2Ans == (0xC0|MCMD_RXParamSetupAns_RX2DataRateACK|MCMD_RXParamSetupAns_ChannelACK| MCMD_RXParamSetupAns_RX1DrOffsetAck) ) { LMIC.dn2Dr = dr; LMIC.dn2Freq = freq; LMIC.rx1DrOffset = rx1DrOffset; DO_DEVDB(LMIC.dn2Dr,dn2Dr); DO_DEVDB(LMIC.dn2Freq,dn2Freq); } -#endif // !DISABLE_MCMD_DN2P_SET - oidx += 5; - continue; + + /* put the first copy of the message */ + response_fit = put_mac_uplink_byte2(MCMD_RXParamSetupAns, LMIC.dn2Ans & ~MCMD_RXParamSetupAns_RFU); + break; } - case MCMD_DCAP_REQ: { -#if !defined(DISABLE_MCMD_DCAP_REQ) +#endif // !DISABLE_MCMD_RXParamSetupReq + +#if !defined(DISABLE_MCMD_RXTimingSetupReq) + case MCMD_RXTimingSetupReq: { + u1_t delay = opts[oidx+1] & MCMD_RXTimingSetupReq_Delay; + if (delay == 0) + delay = 1; + + LMIC.rxDelay = delay; + LMIC.macRxTimingSetupAns = 2; + response_fit = put_mac_uplink_byte(MCMD_RXTimingSetupAns); + break; + } +#endif // !DISABLE_MCMD_RXTimingSetupReq + +#if !defined(DISABLE_MCMD_DutyCycleReq) + case MCMD_DutyCycleReq: { u1_t cap = opts[oidx+1]; - // A value cap=0xFF means device is OFF unless enabled again manually. - if( cap==0xFF ) - LMIC.opmode |= OP_SHUTDOWN; // stop any sending LMIC.globalDutyRate = cap & 0xF; LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); - LMIC.dutyCapAns = 1; - oidx += 2; -#endif // !DISABLE_MCMD_DCAP_REQ - continue; + + response_fit = put_mac_uplink_byte(MCMD_DutyCycleAns); + break; } - case MCMD_SNCH_REQ: { -#if !defined(DISABLE_MCMD_SNCH_REQ) +#endif // !DISABLE_MCMD_DutyCycleReq + +#if !defined(DISABLE_MCMD_NewChannelReq) && CFG_LMIC_EU_like + case MCMD_NewChannelReq: { u1_t chidx = opts[oidx+1]; // channel + u4_t raw_f_not_zero = opts[oidx+2] | opts[oidx+3] | opts[oidx+4]; u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); // freq u1_t drs = opts[oidx+5]; // datarate span - LMIC.snchAns = 0x80; - if( freq != 0 && LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(drs&0xF,drs>>4), -1) ) - LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK; -#endif // !DISABLE_MCMD_SNCH_REQ - oidx += 6; - continue; + u1_t ans = MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK; + + if (freq == 0 && raw_f_not_zero) { + ans &= ~MCMD_NewChannelAns_ChannelACK; + } + u1_t MaxDR = drs >> 4; + u1_t MinDR = drs & 0xF; + if (MaxDR < MinDR || !validDR(MaxDR) || !validDR(MinDR)) { + ans &= ~MCMD_NewChannelAns_DataRateACK; + } + + if( ans == (MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK)) { + if ( ! LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(MinDR, MaxDR), -1) ) { + LMICOS_logEventUint32("NewChannelReq: setupChannel failed", (MaxDR << 24u) | (MinDR << 16u) | (raw_f_not_zero << 8) | (chidx << 0)); + ans &= ~MCMD_NewChannelAns_ChannelACK; + } + } + + response_fit = put_mac_uplink_byte2(MCMD_NewChannelAns, ans); + break; } - case MCMD_PING_SET: { -#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) +#endif // !DISABLE_MCMD_NewChannelReq + +#if !defined(DISABLE_MCMD_DlChannelReq) && CFG_LMIC_EU_like + case MCMD_DlChannelReq: { + u1_t chidx = opts[oidx+1]; // channel + u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); // freq + u1_t ans = MCMD_DlChannelAns_FreqACK|MCMD_DlChannelAns_ChannelACK; + + if (freq == 0) { + ans &= ~MCMD_DlChannelAns_ChannelACK; + } + if (chidx > MAX_CHANNELS) { + // this is not defined by the 1.0.3 spec + ans = 0; + } else if ((LMIC.channelMap & (1 << chidx)) == 0) { + // the channel is not enabled for downlink. + ans &= ~MCMD_DlChannelAns_FreqACK; + } + + if( ans == (MCMD_DlChannelAns_FreqACK|MCMD_DlChannelAns_ChannelACK)) { + LMIC.channelDlFreq[chidx] = freq; + } + + response_fit = put_mac_uplink_byte2(MCMD_DlChannelAns, ans); + // set sticky answer. + LMIC.macDlChannelAns = ans | 0xC0; + break; + } +#endif // !DISABLE_MCMD_DlChannelReq + +#if !defined(DISABLE_MCMD_PingSlotChannelReq) && !defined(DISABLE_PING) + case MCMD_PingSlotChannelReq: { + u4_t raw_f_not_zero = opts[oidx+1] | opts[oidx+2] | opts[oidx+3]; u4_t freq = LMICbandplan_convFreq(&opts[oidx+1]); - u1_t flags = 0x80; - if( freq != 0 ) { - flags |= MCMD_PING_ANS_FQACK; + u1_t dr = opts[oidx+4] & 0xF; + u1_t ans = MCMD_PingSlotFreqAns_DataRateACK|MCMD_PingSlotFreqAns_ChannelACK; + if (! raw_f_not_zero) { + freq = FREQ_PING; + } else if (freq == 0) { + ans &= ~MCMD_PingSlotFreqAns_ChannelACK; + } + if (! validDR(dr)) + ans &= ~MCMD_PingSlotFreqAns_DataRateACK; + + if (ans == (MCMD_PingSlotFreqAns_DataRateACK|MCMD_PingSlotFreqAns_ChannelACK)) { LMIC.ping.freq = freq; + LMIC.ping.dr = dr; DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); DO_DEVDB(LMIC.ping.freq, pingFreq); DO_DEVDB(LMIC.ping.dr, pingDr); } - LMIC.pingSetAns = flags; -#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING - oidx += 4; - continue; + response_fit = put_mac_uplink_byte2(MCMD_PingSlotChannelAns, ans); + break; } - case MCMD_BCNI_ANS: { -#if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS) +#endif // !DISABLE_MCMD_PingSlotChannelReq && !DISABLE_PING + +#if defined(ENABLE_MCMD_BeaconTimingAns) && !defined(DISABLE_BEACONS) + case MCMD_BeaconTimingAns: { // Ignore if tracking already enabled if( (LMIC.opmode & OP_TRACK) == 0 ) { LMIC.bcnChnl = opts[oidx+3]; @@ -680,13 +1068,13 @@ scan_mac_cmds( ASSERT(LMIC.bcninfoTries!=0); // Setup RX parameters LMIC.bcninfo.txtime = (LMIC.rxtime - + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BCNI_TUNIT) - + ms2osticksCeil(MCMD_BCNI_TUNIT/2) + + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BeaconTimingAns_TUNIT) + + ms2osticksCeil(MCMD_BeaconTimingAns_TUNIT/2) - BCN_INTV_osticks); LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared) - calcBcnRxWindowFromMillis(MCMD_BCNI_TUNIT,1); // error of +/-N ms + calcBcnRxWindowFromMillis(MCMD_BeaconTimingAns_TUNIT,1); // error of +/-N ms - EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BCNI_ANS, + EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BeaconTimingAns, e_.eui = MAIN::CDEV->getEui(), e_.lostmic = Base::lsbf4(&d[pend]), e_.info = (LMIC.missedBcns | @@ -694,27 +1082,27 @@ scan_mac_cmds( - LMIC.bcnRxtime) << 8)), e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); } -#endif // !DISABLE_MCMD_BCNI_ANS && !DISABLE_BEACONS - oidx += 4; - continue; + break; } /* end case */ - case MCMD_TxParamSetupReq: { +#endif // !ENABLE_MCMD_BeaconTimingAns && !DISABLE_BEACONS + #if LMIC_ENABLE_TxParamSetupReq + case MCMD_TxParamSetupReq: { uint8_t txParam; txParam = opts[oidx+1]; - // we don't allow unrecognized bits to come through + // we don't allow unrecognized bits to get to txParam. txParam &= (MCMD_TxParam_RxDWELL_MASK| MCMD_TxParam_TxDWELL_MASK| MCMD_TxParam_MaxEIRP_MASK); LMIC.txParam = txParam; - LMIC.txParamSetupAns = 1; -#endif // LMIC_ENABLE_TxParamSetupReq - oidx += 2; - continue; + response_fit = put_mac_uplink_byte(MCMD_TxParamSetupAns); + break; } /* end case */ - case MCMD_DeviceTimeAns: { +#endif // LMIC_ENABLE_TxParamSetupReq + #if LMIC_ENABLE_DeviceTimeReq + case MCMD_DeviceTimeAns: { // don't process a spurious downlink. if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_rx ) { // remember that it's time to notify the client. @@ -741,22 +1129,35 @@ scan_mac_cmds( LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": MAC command DeviceTimeAns received: seconds_since_gps_epoch=%"PRIu32", fractional_seconds=%d\n", os_getTime(), LMIC.netDeviceTime, LMIC.netDeviceTimeFrac); #endif } + break; + } /* end case */ #endif // LMIC_ENABLE_DeviceTimeReq - oidx += 6; - continue; + + default: { + // force olen to current oidx so we'll exit the while() + olen = oidx; + break; } /* end case */ } /* end switch */ - /* unrecognized mac commands fall out of switch to here */ - EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = Base::lsbf4(&d[pend]), - e_.info2 = Base::msbf4(&opts[oidx]))); - /* stop processing options */ - break; + + /* if we're out of spce for responses, skip to end. */ + if (! response_fit) { + olen = oidx; + } else { + oidx += cmdlen; + } } /* end while */ + return oidx; } +// change the ADR ack request count, unless adr ack is diabled. +static void setAdrAckCount (s2_t count) { + if (LMIC.adrAckReq != LINK_CHECK_OFF) { + LMIC.adrAckReq = count; + } +} + static bit_t decodeFrame (void) { xref2u1_t d = LMIC.frame; u1_t hdr = d[0]; @@ -765,6 +1166,8 @@ static bit_t decodeFrame (void) { #if LMIC_DEBUG_LEVEL > 0 const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); #endif + if (dlen > 0) + LMICOS_logEventUint32("decodeFrame", (dlen << 8) | (hdr << 0)); if( dlen < OFF_DAT_OPTS+4 || (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || @@ -792,6 +1195,8 @@ static bit_t decodeFrame (void) { int pend = dlen-4; // MIC if( addr != LMIC.devaddr ) { + LMICOS_logEventUint32("decodeFrame: wrong address", addr); + EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS, e_.eui = MAIN::CDEV->getEui(), e_.info = addr, @@ -799,6 +1204,7 @@ static bit_t decodeFrame (void) { goto norx; } if( poff > pend ) { + LMICOS_logEventUint32("decodeFrame: corrupted frame", (dlen << 16) | (fct << 8) | (poff - pend)); EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, e_.eui = MAIN::CDEV->getEui(), e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16))); @@ -811,9 +1217,21 @@ static bit_t decodeFrame (void) { if( pend > poff ) port = d[poff++]; - seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn); + // compute the 32-bit sequence number based on the 16-bit sequence number received + // and the internal 32-bit number. Because the 32-bit number is used in the MIC + // calculation, this must be right. (And if you're curious why a 32-bit seqno matters, + // it's this calculation, plus its use in the MIC calculation.) + // + // we have to be careful to get the right value for replay of last message received. + u2_t seqnoDiff = (u2_t)(seqno - LMIC.seqnoDn); + if (seqnoDiff == 0xFFFFu) { + seqno = LMIC.seqnoDn - 1; + } else { + seqno = LMIC.seqnoDn + seqnoDiff; + } if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) { + LMICOS_logEventUint32("decodeFrame: bad MIC", os_rlsbf4(&d[pend])); EV(spe3Cond, ERR, (e_.reason = EV::spe3Cond_t::CORRUPTED_MIC, e_.eui1 = MAIN::CDEV->getEui(), e_.info1 = Base::lsbf4(&d[pend]), @@ -827,20 +1245,29 @@ static bit_t decodeFrame (void) { e_.eui = MAIN::CDEV->getEui(), e_.info = LMIC.seqnoDn, e_.info2 = seqno)); + LMICOS_logEventUint32("decodeFrame: rollover discarded", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); goto norx; } - if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) { + if( seqno != LMIC.seqnoDn-1 || !LMIC.lastDnConf || ftype != HDR_FTYPE_DCDN ) { EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE, e_.eui = MAIN::CDEV->getEui(), e_.info = LMIC.seqnoDn, e_.info2 = seqno)); + LMICOS_logEventUint32("decodeFrame: Retransmit confimed discarded", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); goto norx; } // Replay of previous sequence number allowed only if // previous frame and repeated both requested confirmation + // but set a flag, so we don't actually process the message. + LMICOS_logEventUint32("decodeFrame: Retransmit confimed accepted", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); replayConf = 1; + LMIC.dnConf = FCT_ACK; } else { + if( seqnoDiff > LMICbandplan_MAX_FCNT_GAP) { + LMICOS_logEventUint32("decodeFrame: gap too big", (seqnoDiff << 16) | (seqno & 0xFFFFu)); + goto norx; + } if( seqno > LMIC.seqnoDn ) { EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP, e_.eui = MAIN::CDEV->getEui(), @@ -850,7 +1277,18 @@ static bit_t decodeFrame (void) { LMIC.seqnoDn = seqno+1; // next number to be expected DO_DEVDB(LMIC.seqnoDn,seqnoDn); // DN frame requested confirmation - provide ACK once with next UP frame - LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); + LMIC.dnConf = LMIC.lastDnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); + if (LMIC.dnConf) + LMICOS_logEventUint32("decodeFrame: Confirmed downlink", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); + } + + if (port == 0 && olen != 0 && pend > poff) { + // we have a port-zero message, and piggyback mac data. + // discard, section 4.3.1.6 line 544-546 +#if LMIC_DEBUG_LEVEL > 0 + LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": port==0 && FOptsLen=%#x: discard\n", os_getTime(), olen); +#endif + goto norx; } if( LMIC.dnConf || (fct & FCT_MORE) ) @@ -858,25 +1296,32 @@ static bit_t decodeFrame (void) { // We heard from network LMIC.adrChanged = LMIC.rejoinCnt = 0; - if( LMIC.adrAckReq != LINK_CHECK_OFF ) - LMIC.adrAckReq = LINK_CHECK_INIT; + setAdrAckCount(LINK_CHECK_INIT); +#if !defined(DISABLE_MCMD_RXParamSetupReq) + // We heard from network "on a Class A downlink" + LMIC.dn2Ans = 0; +#endif // !defined(DISABLE_MCMD_RXParamSetupReq) +#if !defined(DISABLE_MCMD_RXTimingSetupReq) + // We heard from network "on a Class A downlink" + LMIC.macRxTimingSetupAns = 0; +#endif // !defined(DISABLE_MCMD_RXParamSetupReq) +#if !defined(DISABLE_MCMD_DlChannelReq) && CFG_LMIC_EU_like + LMIC.macDlChannelAns = 0; +#endif int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps); // for legacy reasons, LMIC.margin is set to the unsigned sensitivity. It can never be negative. // it's only computed for legacy clients LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m; -#if LMIC_DEBUG_LEVEL > 0 - // Process OPTS - LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": process options (olen=%#x)\n", os_getTime(), olen); -#endif - + // even if it's a replay confirmed, we process the mac options. xref2u1_t opts = &d[OFF_DAT_OPTS]; - int oidx = scan_mac_cmds(opts, olen); + int oidx = scan_mac_cmds(opts, olen, port); if( oidx != olen ) { EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, e_.eui = MAIN::CDEV->getEui(), e_.info = 0x1000000 + (oidx) + (olen<<8))); + oidx = olen; } if( !replayConf ) { @@ -889,7 +1334,7 @@ static bit_t decodeFrame (void) { #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": process mac commands for port 0 (olen=%#x)\n", os_getTime(), pend-poff); #endif - int optendindex = scan_mac_cmds(d+poff, pend-poff); + int optendindex = scan_mac_cmds(d+poff, pend-poff, port); if (optendindex != pend-poff) { #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF( @@ -899,6 +1344,7 @@ static bit_t decodeFrame (void) { ); #endif } + // wait to transmit until txcomplete: above. } } // end decrypt payload EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), @@ -917,6 +1363,9 @@ static bit_t decodeFrame (void) { e_.eui = MAIN::CDEV->getEui(), e_.info = Base::lsbf4(&d[pend]), e_.info2 = seqno)); + // discard the data + LMICOS_logEventUint32("decodeFrame: discarding replay", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); + goto norx; } if( // NWK acks but we don't have a frame pending @@ -930,7 +1379,7 @@ static bit_t decodeFrame (void) { e_.info = seqno, e_.info2 = ackup)); #if LMIC_DEBUG_LEVEL > 1 - LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": ??ack error ack=%d txCnt=%d\n", os_getTime(), ackup, LMIC.txCnt); + LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": ??ack error ack=%d txCnt=%d\n", os_getTime(), ackup, LMIC.txCnt); #endif } @@ -956,45 +1405,71 @@ static bit_t decodeFrame (void) { // ================================================================================ // TX/RX transaction support +// start reception and log. +static void radioRx (void) { + reportEventNoUpdate(EV_RXSTART); + os_radio(RADIO_RX); +} +// start RX in window 2. static void setupRx2 (void) { initTxrxFlags(__func__, TXRX_DNW2); LMIC.rps = dndr2rps(LMIC.dn2Dr); LMIC.freq = LMIC.dn2Freq; LMIC.dataLen = 0; - os_radio(RADIO_RX); + radioRx(); } +ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym) { + if (LMIC.client.clockError != 0) { + // Calculate how much the clock will drift maximally after delay has + // passed. This indicates the amount of time we can be early + // _or_ late. + ostime_t drift = (int64_t)delay * LMIC.client.clockError / MAX_CLOCK_ERROR; + + // Increase the receive window by twice the maximum drift (to + // compensate for a slow or a fast clock). + delay -= drift; + + // adjust rxsyms (the size of the window in syms) according to our + // uncertainty. do this in a strange order to avoid a divide if we can. + // rely on hsym = Tsym / 2 + if ((255 - LMIC.rxsyms) * hsym < drift) { + LMIC.rxsyms = 255; + } else { + LMIC.rxsyms = (u1_t) (LMIC.rxsyms + drift / hsym); + } + } + return delay; +} + +ostime_t LMICcore_RxWindowOffset (ostime_t hsym, u1_t rxsyms_in) { + ostime_t const Tsym = 2 * hsym; + ostime_t rxsyms; + ostime_t rxoffset; + + rxsyms = ((2 * (int)rxsyms_in - 8) * Tsym + LMICbandplan_RX_ERROR_ABS_osticks * 2 + Tsym - 1) / Tsym; + if (rxsyms < rxsyms_in) { + rxsyms = rxsyms_in; + } + LMIC.rxsyms = (u1_t) rxsyms; + + rxoffset = (8 - rxsyms) * hsym - LMICbandplan_RX_EXTRA_MARGIN_osticks; + + return rxoffset; +} static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { ostime_t hsym = dr2hsym(dr); - LMIC.rxsyms = MINRX_SYMS; - - // If a clock error is specified, compensate for it by extending the - // receive window - if (LMIC.clockError != 0) { - // Calculate how much the clock will drift maximally after delay has - // passed. This indicates the amount of time we can be early - // _or_ late. - ostime_t drift = (int64_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; - - // Increase the receive window by twice the maximum drift (to - // compensate for a slow or a fast clock). - // decrease the rxtime to compensate for. Note that hsym is a - // *half* symbol time, so the factor 2 is hidden. First check if - // this would overflow (which can happen if the drift is very - // high, or the symbol time is low at high datarates). - if ((255 - LMIC.rxsyms) * hsym < drift) - LMIC.rxsyms = 255; - else - LMIC.rxsyms += drift / hsym; - - } - - // Center the receive window on the center of the expected preamble + // Center the receive window on the center of the expected preamble and timeout. // (again note that hsym is half a sumbol time, so no /2 needed) - LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; + // we leave RX_RAMPUP unadjusted for the clock drift. The IBM LMIC generates delays + // that are too long for SF12, and too short for other SFs, so we follow the + // Semtech reference code. + // + // This also sets LMIC.rxsyms. + LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA), hsym); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - RX_RAMPUP); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); @@ -1006,7 +1481,7 @@ static void setupRx1 (osjobcb_t func) { LMIC.rps = setNocrc(LMIC.rps,1); LMIC.dataLen = 0; LMIC.osjob.func = func; - os_radio(RADIO_RX); + radioRx(); } @@ -1019,8 +1494,10 @@ static void txDone (ostime_t delay, osjobcb_t func) { } #endif // !DISABLE_PING - // Change RX frequency / rps (US only) before we increment txChnl + // Change RX frequency (can happen even for EU-like if programmed by DlChannelReq) + // change params and rps (US only) before we increment txChnl LMICbandplan_setRx1Params(); + // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune // into the middle of the 8 symbols preamble. @@ -1033,7 +1510,6 @@ static void txDone (ostime_t delay, osjobcb_t func) { } } - // ======================================== Join frames @@ -1043,53 +1519,19 @@ static void onJoinFailed (xref2osjob_t osjob) { // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. - reportEvent(EV_JOIN_FAILED); + reportEventAndUpdate(EV_JOIN_FAILED); } - +// process join-accept message or deal with no join-accept in slot 2. static bit_t processJoinAccept (void) { - ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0); + if ((LMIC.txrxFlags & TXRX_DNW1) != 0 && LMIC.dataLen == 0) + return 0; + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); if( LMIC.dataLen == 0 ) { - nojoinframe: - if( (LMIC.opmode & OP_JOINING) == 0 ) { - ASSERT((LMIC.opmode & OP_REJOIN) != 0); - // REJOIN attempt for roaming - LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); - if( LMIC.rejoinCnt < 10 ) - LMIC.rejoinCnt++; - reportEvent(EV_REJOIN_FAILED); - return 1; - } - LMIC.opmode &= ~OP_TXRXPEND; - int failed = LMICbandplan_nextJoinState(); - EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.datarate|DR_PAGE, - e_.info2 = failed)); - // Build next JOIN REQUEST with next engineUpdate call - // Optionally, report join failed. - // Both after a random/chosen amount of ticks. That time - // is in LMIC.txend. The delay here is either zero or 1 - // tick; onJoinFailed()/runEngineUpdate() are responsible - // for honoring that. XXX(tmm@mcci.com) The IBM 1.6 code - // claimed to return a delay but really returns 0 or 1. - // Once we update as923 to return failed after dr2, we - // can take out this #if. -#if CFG_region != LMIC_REGION_as923 - os_setTimedCallback(&LMIC.osjob, os_getTime()+failed, - failed - ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed - : FUNC_ADDR(runEngineUpdate)); // next step to be delayed -#else - // in the join of AS923 v1.1 older, only DR2 is used. Therefore, - // not much improvement when it handles two different behavior; - // onJoinFailed or runEngineUpdate. - os_setTimedCallback(&LMIC.osjob, os_getTime()+failed, - FUNC_ADDR(onJoinFailed)); -#endif - return 1; + // we didn't get any data and we're in slot 2. So... there's no join frame. + return processJoinAccept_nojoinframe(); } u1_t hdr = LMIC.frame[0]; u1_t dlen = LMIC.dataLen; @@ -1102,16 +1544,13 @@ static bit_t processJoinAccept (void) { e_.eui = MAIN::CDEV->getEui(), e_.info = dlen < 4 ? 0 : mic, e_.info2 = hdr + (dlen<<8))); - badframe: - if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) - return 0; - goto nojoinframe; + return processJoinAccept_badframe(); } aes_encrypt(LMIC.frame+1, dlen-1); if( !aes_verifyMic0(LMIC.frame, dlen-4) ) { EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC, e_.info = mic)); - goto badframe; + return processJoinAccept_badframe(); } u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); @@ -1158,16 +1597,21 @@ static bit_t processJoinAccept (void) { ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0); // // XXX(tmm@mcci.com) OP_REJOIN confuses me, and I'm not sure why we're - // adjusting DRs here. We've just recevied a join accept, and the - // datarate therefore shouldn't be in play. + // adjusting DRs here. We've just received a join accept, and the + // datarate therefore shouldn't be in play. In effect, we set the + // initial data rate based on the number of times we tried to rejoin. // if( (LMIC.opmode & OP_REJOIN) != 0 ) { #if CFG_region != LMIC_REGION_as923 - // TODO(tmm@mcci.com) regionalize + // TODO(tmm@mcci.com) regionalize // Lower DR every try below current UP DR + // need to check feasibility? join feasability is default. LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); #else // in the join of AS923 v1.1 or older, only DR2 (SF10) is used. + // TODO(tmm@mcci.com) if the rejoin logic is at all correct, we + // should be setting the uplink datarate based on the number of + // tries; this doesn't set the AS923 join data rate. LMIC.datarate = AS923_DR_SF10; #endif } @@ -1175,14 +1619,66 @@ static bit_t processJoinAccept (void) { LMIC.opmode |= OP_NEXTCHNL; LMIC.txCnt = 0; stateJustJoined(); + // transition to the ADR_ACK initial state. + setAdrAckCount(LINK_CHECK_INIT); + LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; LMIC.rx1DrOffset = (LMIC.frame[OFF_JA_DLSET] >> 4) & 0x7; LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; - reportEvent(EV_JOINED); + reportEventAndUpdate(EV_JOINED); return 1; } +static bit_t processJoinAccept_badframe(void) { + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + // continue the join process: there's another window. + return 0; + else + // stop the join process + return processJoinAccept_nojoinframe(); +} + +static bit_t processJoinAccept_nojoinframe(void) { + // Valid states are JOINING (in which caise REJOIN is ignored) + // or ~JOINING and REJOIN. If it's a REJOIN, + // we need to turn off rejoin, signal an event, and increment + // the rejoin-sent count. Internal callers will turn on rejoin + // occasionally. + if( (LMIC.opmode & OP_JOINING) == 0) { + ASSERT((LMIC.opmode & OP_REJOIN) != 0); + LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); + if( LMIC.rejoinCnt < 10 ) + LMIC.rejoinCnt++; + reportEventAndUpdate(EV_REJOIN_FAILED); + // stop the join process. + return 1; + } + // otherwise it's a normal join. At end of rx2, so we + // need to schedule something. + LMIC.opmode &= ~OP_TXRXPEND; + reportEventNoUpdate(EV_JOIN_TXCOMPLETE); + int failed = LMICbandplan_nextJoinState(); + EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.datarate|DR_PAGE, + e_.info2 = failed)); + // Build next JOIN REQUEST with next engineUpdate call + // Optionally, report join failed. + // Both after a random/chosen amount of ticks. That time + // is in LMIC.txend. The delay here is either zero or 1 + // tick; onJoinFailed()/runEngineUpdate() are responsible + // for honoring that. XXX(tmm@mcci.com) The IBM 1.6 code + // claimed to return a delay but really returns 0 or 1. + // Once we update as923 to return failed after dr2, we + // can take out this #if. + os_setTimedCallback(&LMIC.osjob, os_getTime()+failed, + failed + ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed + : FUNC_ADDR(runEngineUpdate)); // next step to be delayed + // stop this join process. + return 1; +} static void processRx2Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); @@ -1190,7 +1686,9 @@ static void processRx2Jacc (xref2osjob_t osjob) { if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot } - processJoinAccept(); + // we're done with this join cycle anyway, so ignore the + // result of processJoinAccept() + (void) processJoinAccept(); } @@ -1230,23 +1728,20 @@ static void jreqDone (xref2osjob_t osjob) { // Fwd decl. static bit_t processDnData(void); -static void processRx2DnDataDelay (xref2osjob_t osjob) { - LMIC_API_PARAMETER(osjob); - - processDnData(); -} - static void processRx2DnData (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); if( LMIC.dataLen == 0 ) { initTxrxFlags(__func__, 0); // nothing in 1st/2nd DN slot - // Delay callback processing to avoid up TX while gateway is txing our missed frame! - // Since DNW2 uses SF12 by default we wait 3 secs. - os_setTimedCallback(&LMIC.osjob, - (os_getTime() + DNW2_SAFETY_ZONE + LMICcore_rndDelay(2)), - FUNC_ADDR(processRx2DnDataDelay)); - return; + // It could be that the gateway *is* sending a reply, but we + // just didn't pick it up. To avoid TX'ing again while the + // gateay is not listening anyway, delay the next transmission + // until DNW2_SAFETY_ZONE from now, and add up to 2 seconds of + // extra randomization. + // BUG(tmm@mcci.com) this delay is not needed for some + // regions, e.g. US915 and AU915, which have non-overlapping + // uplink and downlink. + txDelay(os_getTime() + DNW2_SAFETY_ZONE, 2); } processDnData(); } @@ -1283,84 +1778,73 @@ static void updataDone (xref2osjob_t osjob) { // ======================================== +static bit_t sendAdrAckReq(void) { + if (LMIC.adrAckReq < LINK_CHECK_CONT) { + return 0; + } else if (LMIC.adrAckReq <= LINK_CHECK_DEAD) { + return 1; + } else if (LMIC.adrAckReq <= LINK_CHECK_DEAD + 32) { + // for compliance, though it's not clear why they care, we stop sending requests + // when we're right at the DEAD state + return 0; + } else if (LMIC.adrAckReq <= LINK_CHECK_UNJOIN - 32) { + return 0; + } else { + // otherwise, if our alternative is to unjoin and we have no other info, keep + // asking for a downlink. + return 1; + } +} -static void buildDataFrame (void) { +static bit_t buildDataFrame (void) { bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL); u1_t dlen = txdata ? LMIC.pendTxLen : 0; // Piggyback MAC options // Prioritize by importance + // highest importance are the ones in the pendMac buffer. int end = OFF_DAT_OPTS; -#if !defined(DISABLE_PING) - if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) { - // Indicate pingability in every UP frame - LMIC.frame[end] = MCMD_PING_IND; - LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4); - end += 2; + + if (LMIC.pendTxPort != 0 && LMIC.pendMacPiggyback && LMIC.pendMacLen != 0) { + os_copyMem(LMIC.frame + end, LMIC.pendMacData, LMIC.pendMacLen); + end += LMIC.pendMacLen; } -#endif // !DISABLE_PING -#if !defined(DISABLE_MCMD_DCAP_REQ) - if( LMIC.dutyCapAns ) { - LMIC.frame[end] = MCMD_DCAP_ANS; - end += 1; - LMIC.dutyCapAns = 0; - } -#endif // !DISABLE_MCMD_DCAP_REQ - if( LMIC.devsAns ) { // answer to device status - LMIC.frame[end+0] = MCMD_DEVS_ANS; - LMIC.frame[end+1] = os_getBattLevel(); - LMIC.frame[end+2] = LMIC.devAnsMargin; - end += 3; - LMIC.devsAns = 0; - } - if( LMIC.ladrAns ) { // answer to ADR change - LMIC.frame[end+0] = MCMD_LADR_ANS; - LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU; - end += 2; - LMIC.ladrAns = 0; - } -#if !defined(DISABLE_BEACONS) - if( LMIC.bcninfoTries > 0 ) { - LMIC.frame[end] = MCMD_BCNI_REQ; - end += 1; - } -#endif // !DISABLE_BEACONS - if( LMIC.adrChanged ) { - if( LMIC.adrAckReq < 0 ) - LMIC.adrAckReq = 0; - LMIC.adrChanged = 0; - } -#if !defined(DISABLE_MCMD_DN2P_SET) + LMIC.pendMacLen = 0; + LMIC.pendMacPiggyback = 0; + +#if !defined(DISABLE_MCMD_RXParamSetupReq) + // per 5.4, RxParamSetupAns is sticky. if (LMIC.dn2Ans) { - LMIC.frame[end + 0] = MCMD_DN2P_ANS; - LMIC.frame[end + 1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; + if (LMIC.dn2Ans & 0x40) { + LMIC.dn2Ans ^= 0x40; + } else { + LMIC.frame[end + 0] = MCMD_RXParamSetupAns; + LMIC.frame[end + 1] = LMIC.dn2Ans & ~MCMD_RXParamSetupAns_RFU; end += 2; - LMIC.dn2Ans = 0; + } } -#endif // !DISABLE_MCMD_DN2P_SET -#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) - if( LMIC.pingSetAns != 0 ) { - LMIC.frame[end+0] = MCMD_PING_ANS; - LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU; - end += 2; - LMIC.pingSetAns = 0; +#endif // !DISABLE_MCMD_RXParamSetupReq +#if !defined(DISABLE_MCMD_DlChannelReq) + // per 5.4, DlChannelAns is sticky. + if (LMIC.macDlChannelAns) { + if (LMIC.macDlChannelAns & 0x40) { + LMIC.macDlChannelAns ^= 0x40; + } else { + LMIC.frame[end + 0] = MCMD_DlChannelAns; + LMIC.frame[end + 1] = LMIC.macDlChannelAns & ~MCMD_DlChannelAns_RFU; + end += 2; + } } -#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING -#if !defined(DISABLE_MCMD_SNCH_REQ) - if( LMIC.snchAns ) { - LMIC.frame[end+0] = MCMD_SNCH_ANS; - LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU; - end += 2; - LMIC.snchAns = 0; +#endif // !DISABLE_MCMD_DlChannelReq +#if !defined(DISABLE_MCMD_RXTimingSetupReq) + // per 5.7, RXTimingSetupAns is sticky + if (LMIC.macRxTimingSetupAns == 2) { + LMIC.macRxTimingSetupAns = 1; + } else if (LMIC.macRxTimingSetupAns) { + LMIC.frame[end++] = MCMD_RXTimingSetupAns; } -#endif // !DISABLE_MCMD_SNCH_REQ -#if LMIC_ENABLE_TxParamSetupReq - if ( LMIC.txParamSetupAns ) { - LMIC.frame[end+0] = MCMD_TxParamSetupAns; - end += 1; - LMIC.txParamSetupAns = 0; - } -#endif +#endif // !DISABLE_MCMD_RXTimingSetupReq) + #if LMIC_ENABLE_DeviceTimeReq if ( LMIC.txDeviceTimeReqState == lmic_RequestTimeState_tx ) { LMIC.frame[end+0] = MCMD_DeviceTimeReq; @@ -1368,7 +1852,25 @@ static void buildDataFrame (void) { LMIC.txDeviceTimeReqState = lmic_RequestTimeState_rx; } #endif // LMIC_ENABLE_DeviceTimeReq - ASSERT(end <= OFF_DAT_OPTS+16); +#if !defined(DISABLE_BEACONS) && defined(ENABLE_MCMD_BeaconTimingAns) + if ( LMIC.bcninfoTries > 0 ) { + LMIC.frame[end+0] = MCMD_BeaconInfoReq; + end += 1; + } +#endif + if (end > OFF_DAT_OPTS + 16) { + LMICOS_logEventUint32("piggyback mac opts too long", end); + return 0; + } + + if( LMIC.adrChanged ) { + // if ADR is enabled, and we were just counting down the + // transmits before starting an ADR, advance the timer so + // we'll do an ADR now. + if (LMIC.adrAckReq < LINK_CHECK_CONT) + setAdrAckCount(LINK_CHECK_CONT); + LMIC.adrChanged = 0; + } u1_t flen = end + (txdata ? 5+dlen : 4); if( flen > MAX_LEN_FRAME ) { @@ -1376,21 +1878,30 @@ static void buildDataFrame (void) { txdata = 0; flen = end+4; } + + u1_t maxFlen = LMICbandplan_maxFrameLen(LMIC.datarate); + + if (flen > maxFlen) { + LMICOS_logEventUint32("frame too long for this bandplan", (dlen << 16) | (flen << 8) | maxFlen); + return 0; + } + LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled - | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0) + | (sendAdrAckReq() ? FCT_ADRACKReq : 0) | (end-OFF_DAT_OPTS)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); - if( LMIC.txCnt == 0 ) { + if( LMIC.txCnt == 0 && LMIC.upRepeatCount == 0 ) { LMIC.seqnoUp += 1; DO_DEVDB(LMIC.seqnoUp,seqnoUp); } else { + LMICOS_logEventUint32("retransmit", (LMIC.frame[OFF_DAT_FCT] << 24u) | (LMIC.txCnt << 16u) | (LMIC.upRepeatCount << 8u) | (LMIC.upRepeat<<0u)); EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX, e_.eui = MAIN::CDEV->getEui(), e_.info = LMIC.seqnoUp-1, e_.info2 = ((LMIC.txCnt+1) | - (TABLE_GET_U1(DRADJUST, LMIC.txCnt+1) << 8) | + (LMIC.upRepeatCount << 8) | ((LMIC.datarate|DR_PAGE)<<16)))); } os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1); @@ -1403,6 +1914,11 @@ static void buildDataFrame (void) { // Confirmed only makes sense if we have a payload (or at least a port) LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1; if( LMIC.txCnt == 0 ) LMIC.txCnt = 1; + } else if (LMIC.upRepeat != 0) { + // we are repeating. So we need to count here. + if (LMIC.upRepeatCount == 0) { + LMIC.upRepeatCount = 1; + } } LMIC.frame[end] = LMIC.pendTxPort; os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen); @@ -1424,6 +1940,7 @@ static void buildDataFrame (void) { e_.opts.length = end-LORA::OFF_DAT_OPTS, memcpy(&e_.opts[0], LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS))); LMIC.dataLen = flen; + return 1; } @@ -1438,10 +1955,10 @@ static void onBcnRx (xref2osjob_t osjob) { if( LMIC.dataLen == 0 ) { // Nothing received - timeout LMIC.opmode &= ~(OP_SCAN | OP_TRACK); - reportEvent(EV_SCAN_TIMEOUT); + reportEventAndUpdate(EV_SCAN_TIMEOUT); return; } - if( decodeBeacon() <= 0 ) { + if( ! LMIC_BEACON_SUCCESSFUL(decodeBeacon()) ) { // Something is wrong with the beacon - continue scan LMIC.dataLen = 0; os_radio(RADIO_RXON); @@ -1454,7 +1971,7 @@ static void onBcnRx (xref2osjob_t osjob) { calcBcnRxWindowFromMillis(13,1); LMIC.opmode &= ~OP_SCAN; // turn SCAN off LMIC.opmode |= OP_TRACK; // auto enable tracking - reportEvent(EV_BEACON_FOUND); // can be disabled in callback + reportEventAndUpdate(EV_BEACON_FOUND); // can be disabled in callback } @@ -1481,7 +1998,7 @@ bit_t LMIC_enableTracking (u1_t tryBcnInfo) { if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 ) return 0; // already in progress or failed to enable // If BCN info requested from NWK then app has to take are - // of sending data up so that MCMD_BCNI_REQ can be attached. + // of sending data up so that MCMD_BeaconInfoReq can be attached. if( (LMIC.bcninfoTries = tryBcnInfo) == 0 ) startScan(); return 1; // enabled @@ -1554,28 +2071,63 @@ static void buildJoinRequest (u1_t ftype) { static void startJoining (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); - reportEvent(EV_JOINING); + // see issue #244: for backwards compatibility + // don't override what the user does after os_init(). + if (LMIC.initBandplanAfterReset) + LMICbandplan_resetDefaultChannels(); + else + LMIC.initBandplanAfterReset = 1; + + // let the client know that now's the time to update + // network settings. + reportEventAndUpdate(EV_JOINING); +} + +// reset the joined-to-network state (and clean up) +void LMIC_unjoin(void) { + // reset any joining flags + LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_UNJOIN); + + // put us in unjoined state: + LMIC.devaddr = 0; + + // clear transmit. + LMIC_clrTxData(); } // Start join procedure if not already joined. bit_t LMIC_startJoining (void) { if( LMIC.devaddr == 0 ) { // There should be no TX/RX going on - ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0); + // ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0); + LMIC.opmode &= ~OP_POLL; // Lift any previous duty limitation LMIC.globalDutyRate = 0; // Cancel scanning - LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); + LMIC.opmode &= ~(OP_SCAN|OP_UNJOIN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); // Setup state LMIC.rejoinCnt = LMIC.txCnt = 0; + resetJoinParams(); LMICbandplan_initJoinLoop(); LMIC.opmode |= OP_JOINING; - // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS + // reportEventAndUpdate will call engineUpdate which then starts sending JOIN REQUESTS os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); return 1; } return 0; // already joined } + +static void unjoinAndRejoin(xref2osjob_t osjob) { + LMIC_API_PARAMETER(osjob); + LMIC_unjoin(); + LMIC_startJoining(); +} + +// do a deferred unjoin and rejoin, so not in engineupdate. +void LMIC_unjoinAndRejoin(void) { + os_setCallback(&LMIC.osjob, FUNC_ADDR(unjoinAndRejoin)); +} + #endif // !DISABLE_JOIN @@ -1592,8 +2144,7 @@ static void processPingRx (xref2osjob_t osjob) { if( LMIC.dataLen != 0 ) { initTxrxFlags(__func__, TXRX_PING); if( decodeFrame() ) { - reportEvent(EV_RXCOMPLETE); - return; + reportEventNoUpdate(EV_RXCOMPLETE); } } // Pick next ping slot @@ -1601,90 +2152,233 @@ static void processPingRx (xref2osjob_t osjob) { } #endif // !DISABLE_PING +// process downlink data at close of RX window. Return zero if another RX window +// should be scheduled, non-zero to prevent scheduling of RX2 (if relevant). +// Confusingly, the caller actualyl does some of the calculation, so the answer from +// us is not always totaly right; the rx1 window check ignores our result unless +// LMIC.datalen was non zero before calling. +// +// Inputs: +// LMIC.dataLen number of bytes receieved; 0 --> no message at all received. +// LMIC.txCnt currnt confirmed uplink count, or 0 for unconfirmed. +// LMIC.txrxflags state of play for the Class A engine and message receipt. +// +// and many other flags in txcomplete(). + +// forward references. +static bit_t processDnData_norx(void); +static bit_t processDnData_txcomplete(void); static bit_t processDnData (void) { ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); if( LMIC.dataLen == 0 ) { - norx: - if( LMIC.txCnt != 0 ) { - if( LMIC.txCnt < TXCONF_ATTEMPTS ) { - LMIC.txCnt += 1; - setDrTxpow(DRCHG_NOACK, lowerDR(LMIC.datarate, TABLE_GET_U1(DRADJUST, LMIC.txCnt)), KEEP_TXPOW); - // Schedule another retransmission - txDelay(LMIC.rxtime, RETRY_PERIOD_secs); - LMIC.opmode &= ~OP_TXRXPEND; - engineUpdate(); - return 1; - } - initTxrxFlags(__func__, TXRX_NACK | TXRX_NOPORT); - } else { - // Nothing received - implies no port - initTxrxFlags(__func__, TXRX_NOPORT); - } - if( LMIC.adrAckReq != LINK_CHECK_OFF ) - LMIC.adrAckReq += 1; - LMIC.dataBeg = LMIC.dataLen = 0; - txcomplete: - LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); - -#if LMIC_ENABLE_DeviceTimeReq - lmic_request_time_state_t const requestTimeState = LMIC.txDeviceTimeReqState; - if ( requestTimeState != lmic_RequestTimeState_idle ) { - lmic_request_network_time_cb_t * const pNetworkTimeCb = LMIC.pNetworkTimeCb; - int flagSuccess = (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_success); - LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle; - if (pNetworkTimeCb != NULL) { - // reset the callback, so that the user's routine - // can post another request if desired. - LMIC.pNetworkTimeCb = NULL; - - // call the user's notification routine. - (*pNetworkTimeCb)(LMIC.pNetworkTimeUserData, flagSuccess); - } - } -#endif // LMIC_ENABLE_DeviceTimeReq - - if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { - LMIC.opmode &= ~OP_LINKDEAD; - reportEvent(EV_LINK_ALIVE); - } - reportEvent(EV_TXCOMPLETE); - // If we haven't heard from NWK in a while although we asked for a sign - // assume link is dead - notify application and keep going - if( LMIC.adrAckReq > LINK_CHECK_DEAD ) { - // We haven't heard from NWK for some time although we - // asked for a response for some time - assume we're disconnected. Lower DR one notch. - EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD, - e_.eui = MAIN::CDEV->getEui(), - e_.info = LMIC.adrAckReq)); - setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW); - LMIC.adrAckReq = LINK_CHECK_CONT; - LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; - reportEvent(EV_LINK_DEAD); - } -#if !defined(DISABLE_BEACONS) - // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan - if( LMIC.bcninfoTries > 0 ) { - if( (LMIC.opmode & OP_TRACK) != 0 ) { - reportEvent(EV_BEACON_FOUND); - LMIC.bcninfoTries = 0; - } - else if( --LMIC.bcninfoTries == 0 ) { - startScan(); // NWK did not answer - try scan - } - } -#endif // !DISABLE_BEACONS - return 1; + // if this is an RX1 window, shouldn't we return 0 to schedule + // RX2? in fact, the rx1 caller ignores what we return, and + // norx() doesn't call txcomplete if this is RX1. + return processDnData_norx(); } - if( !decodeFrame() ) { + // if we get here, LMIC.dataLen != 0, so there is some + // traffic. + else if( !decodeFrame() ) { + // if we are in downlink window 1, we need to schedule + // downlink window 2. if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) return 0; - goto norx; + else + // otherwise we are in downlink window 2; we will not + // get any more downlink traffic from this uplink, so we need + // to close the books on this uplink attempt + return processDnData_norx(); + } + // downlink frame was accepted. This means that we're done. Except + // there's one bizarre corner case. If we sent a confirmed message + // and got a downlink that didn't have an ACK, we have to retry. + // It is not clear why the network is permitted to do this; the + // fact that they scheduled a downlink for us during one of the RX + // windows is clear confirmation that the uplink made it to the + // network and was valid. However, compliance checks this, so + // we have to handle it and retransmit. + else if (LMIC.txCnt != 0 && (LMIC.txrxFlags & TXRX_NACK) != 0) + { + // grr. we're confirmed but the network downlink did not + // set the ACK bit. We know txCnt is non-zero, so this + // will immediately fall into the retransmit path. We don't + // want to do this unless it's a confirmed uplink. + return processDnData_norx(); + } + // the transmit of the uplink is really complete. + else { + return processDnData_txcomplete(); } - goto txcomplete; } +// nothing was received this window. +static bit_t processDnData_norx(void) { + if( LMIC.txCnt != 0 ) { + if( LMIC.txCnt < TXCONF_ATTEMPTS ) { + // Per [1.0.3] section 18.4, it is recommended that the device adjust datarate down. + // The spec is not clear about what should happen in case the data size is too large + // for the new frame len, but it seems that we should leave theframe len at the new + // data size. Therefore, we set the new data rate here, and then check at transmit time + // whether the packet is now too large; if so, we abandon the transmission. + LMIC.txCnt += 1; + // becase txCnt was at least 1 when we entered this branch, this if() will be taken + // for txCnt == 3, 5, 7. + if (LMIC.txCnt & 1) { + dr_t adjustedDR; + // lower DR + adjustedDR = decDR(LMIC.datarate); + setDrTxpow(DRCHG_NOACK, adjustedDR, KEEP_TXPOW); + } + + // TODO(tmm@mcci.com): check feasibility of lower datarate + // Schedule another retransmission + txDelay(LMIC.rxtime, RETRY_PERIOD_secs); + LMIC.opmode &= ~OP_TXRXPEND; + engineUpdate(); + return 1; + } + // confirmed uplink is complete without an ack: no port and no flag + initTxrxFlags(__func__, TXRX_NACK | TXRX_NOPORT); + } else if (LMIC.upRepeatCount != 0) { + if (LMIC.upRepeatCount < LMIC.upRepeat) { + LMICOS_logEventUint32("processDnData: repeat", (LMIC.upRepeat<<8u) | (LMIC.upRepeatCount<<0u)); + LMIC.upRepeatCount += 1; + txDelay(os_getTime() + ms2osticks(LMICbandplan_TX_RECOVERY_ms), 0); + LMIC.opmode &= ~OP_TXRXPEND; + engineUpdate(); + return 1; + } + // counted out: nothing received. + initTxrxFlags(__func__, TXRX_NOPORT); + } else { + // Nothing received - implies no port + initTxrxFlags(__func__, TXRX_NOPORT); + } + setAdrAckCount(LMIC.adrAckReq + 1); + LMIC.dataBeg = LMIC.dataLen = 0; + + return processDnData_txcomplete(); +} + +// this Class-A uplink-and-receive cycle is complete. +static bit_t processDnData_txcomplete(void) { + LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); + // turn off all the repeat stuff. + LMIC.txCnt = LMIC.upRepeatCount = 0; + + // if there's pending mac data that's not piggyback, launch it now. + if (LMIC.pendMacLen != 0) { + if (LMIC.pendMacPiggyback) { + LMICOS_logEvent("piggyback mac message"); + LMIC.opmode |= OP_POLL; // send back the mac answers even if there's no data. + } else { + // Every mac command on port 0 requires an uplink, if there's data. + // TODO(tmm@mcci.com) -- this is why we need a queueing structure for + // uplinks. + // open code the logic to build this because we don't want to call + // engineUpdate right now. Data is already in the uplink buffer. + LMIC.pendTxConf = 0; // not confirmed + LMIC.pendTxPort = 0; // port 0 + LMIC.pendTxLen = LMIC.pendMacLen; + LMIC.pendMacLen = 0; // discard mac data! + LMIC.opmode |= OP_TXDATA; + LMICOS_logEvent("port0 mac message"); + } + } + + // Half-duplex gateways can have appreciable turn-around times, + // so we force a wait. It might be nice to randomize this a little, + // so that armies of identical devices will not try to talk all + // at once. This is potentially band-specific, so we let it come + // from the band-plan files. + txDelay(os_getTime() + ms2osticks(LMICbandplan_TX_RECOVERY_ms), 0); + +#if LMIC_ENABLE_DeviceTimeReq + // + // if the DeviceTimeReq FSM is active, we need to move it to idle, + // completing the callback. + // + lmic_request_time_state_t const requestTimeState = LMIC.txDeviceTimeReqState; + if ( requestTimeState != lmic_RequestTimeState_idle ) { + lmic_request_network_time_cb_t * const pNetworkTimeCb = LMIC.client.pNetworkTimeCb; + int flagSuccess = (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_success); + LMIC.txDeviceTimeReqState = lmic_RequestTimeState_idle; + if (pNetworkTimeCb != NULL) { + // reset the callback, so that the user's routine + // can post another request if desired. + LMIC.client.pNetworkTimeCb = NULL; + + // call the user's notification routine. + (*pNetworkTimeCb)(LMIC.client.pNetworkTimeUserData, flagSuccess); + } + } +#endif // LMIC_ENABLE_DeviceTimeReq + + if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { + LMIC.opmode &= ~OP_LINKDEAD; + reportEventNoUpdate(EV_LINK_ALIVE); + } + reportEventAndUpdate(EV_TXCOMPLETE); + // If we haven't heard from NWK in a while although we asked for a sign + // assume link is dead - notify application and keep going + if( LMIC.adrAckReq > LINK_CHECK_DEAD ) { + // We haven't heard from NWK for some time although we + // asked for a response for some time - assume we're disconnected. Lower DR one notch. + EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.adrAckReq)); + dr_t newDr = decDR((dr_t)LMIC.datarate); + // newDr must be feasible; there must be at least + // one channel that supports the new datarate. If not, stay + // at current datarate (which finalizes things). + if (! LMICbandplan_isDataRateFeasible(newDr)) { + LMICOS_logEventUint32("LINK_CHECK_DEAD, new DR not feasible", (newDr << 8) | LMIC.datarate); + newDr = LMIC.datarate; + } + if( newDr == (dr_t)LMIC.datarate) { + // We are already at the minimum datarate + // if the link is already marked dead, we need to join. +#if !defined(DISABLE_JOIN) + if ( LMIC.adrAckReq > LINK_CHECK_UNJOIN ) { + LMIC.opmode |= OP_UNJOIN; + } +#endif // !defined(DISABLE_JOIN) + } else if (newDr == LORAWAN_DR0) { + // the spec says: the ADRACKReq shall not be set if + // the device uses its lowest available data rate. + // (1.0.3, 4.3.1.1, line 458) + // We let the count continue to increase. + } else { + // we successfully lowered the data rate... + // reset so that we'll lower again after the next + // 32 uplinks. + setAdrAckCount(LINK_CHECK_CONT); + } + // Decrease DataRate and restore fullpower. + setDrTxpow(DRCHG_NOADRACK, newDr, pow2dBm(0)); + + // be careful only to report EV_LINK_DEAD once. + u2_t old_opmode = LMIC.opmode; + LMIC.opmode = old_opmode | OP_LINKDEAD; + if (LMIC.opmode != old_opmode) + reportEventNoUpdate(EV_LINK_DEAD); // update? + } +#if !defined(DISABLE_BEACONS) + // If this falls to zero the NWK did not answer our MCMD_BeaconInfoReq commands - try full scan + if( LMIC.bcninfoTries > 0 ) { + if( (LMIC.opmode & OP_TRACK) != 0 ) { + reportEventNoUpdate(EV_BEACON_FOUND); // update? + LMIC.bcninfoTries = 0; + } + else if( --LMIC.bcninfoTries == 0 ) { + startScan(); // NWK did not answer - try scan + } + } +#endif // !DISABLE_BEACONS + return 1; +} #if !defined(DISABLE_BEACONS) static void processBeacon (xref2osjob_t osjob) { @@ -1694,7 +2388,7 @@ static void processBeacon (xref2osjob_t osjob) { u1_t flags = LMIC.bcninfo.flags; ev_t ev; - if( LMIC.dataLen != 0 && decodeBeacon() >= 1 ) { + if( LMIC.dataLen != 0 && LMIC_BEACON_SUCCESSFUL(decodeBeacon()) ) { ev = EV_BEACON_TRACKED; if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) { // We don't have a previous beacon to calc some drift - assume @@ -1734,7 +2428,7 @@ static void processBeacon (xref2osjob_t osjob) { LMIC.opmode |= OP_REJOIN; // try if we can roam to another network if( LMIC.bcnRxsyms > MAX_RXSYMS ) { LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); - reportEvent(EV_LOST_TSYNC); + reportEventAndUpdate(EV_LOST_TSYNC); return; } } @@ -1746,31 +2440,33 @@ static void processBeacon (xref2osjob_t osjob) { if( (LMIC.opmode & OP_PINGINI) != 0 ) rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! #endif // !DISABLE_PING - reportEvent(ev); + reportEventAndUpdate(ev); } - +// job entry: time to start receiving a beacon. static void startRxBcn (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processBeacon); - os_radio(RADIO_RX); + radioRx(); } #endif // !DISABLE_BEACONS #if !defined(DISABLE_PING) +// job entry: time to start receiving in our scheduled downlink slot. static void startRxPing (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); LMIC.osjob.func = FUNC_ADDR(processPingRx); - os_radio(RADIO_RX); + radioRx(); } #endif // !DISABLE_PING -// Decide what to do next for the MAC layer of a device -static void engineUpdate (void) { +// Decide what to do next for the MAC layer of a device. Inner part. +// Only called from outer part. +static void engineUpdate_inner (void) { #if LMIC_DEBUG_LEVEL > 0 LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode); #endif @@ -1783,6 +2479,12 @@ static void engineUpdate (void) { LMIC_startJoining(); return; } + // we're joined but LinkTracking says we're out of luck... + if ( LMIC.devaddr != 0 && (LMIC.opmode & OP_UNJOIN) != 0 ) { + LMIC.opmode &= ~OP_UNJOIN; + LMIC_unjoinAndRejoin(); + return; + } #endif // !DISABLE_JOIN ostime_t now = os_getTime(); @@ -1799,7 +2501,6 @@ static void engineUpdate (void) { #endif // !DISABLE_BEACONS if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { - // Need to TX some data... // Assuming txChnl points to channel which first becomes available again. bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0); // Find next suitable channel and return availability time @@ -1807,6 +2508,7 @@ static void engineUpdate (void) { txbeg = LMIC.txend = LMICbandplan_nextTx(now); LMIC.opmode &= ~OP_NEXTCHNL; } else { + // no need to consider anything but LMIC.txend. txbeg = LMIC.txend; } // Delayed TX or waiting for duty cycle? @@ -1835,12 +2537,11 @@ static void engineUpdate (void) { if( (LMIC.opmode & OP_REJOIN) != 0 ) { #if CFG_region != LMIC_REGION_as923 // in AS923 v1.1 or older, no need to change the datarate. + // otherwise we need to check feasibility. txdr = lowerDR(txdr, LMIC.rejoinCnt); #endif - ftype = HDR_FTYPE_REJOIN; - } else { - ftype = HDR_FTYPE_JREQ; } + ftype = HDR_FTYPE_JREQ; buildJoinRequest(ftype); LMIC.osjob.func = FUNC_ADDR(jreqDone); } else @@ -1867,14 +2568,26 @@ static void engineUpdate (void) { // App code might do some stuff after send unaware of RESET. goto reset; } - buildDataFrame(); + if (! buildDataFrame()) { + // can't transmit this message. Report completion. + initTxrxFlags(__func__, TXRX_LENERR); + if (LMIC.pendTxConf || LMIC.txCnt) { + orTxrxFlags(__func__, TXRX_NACK); + } + LMIC.opmode &= ~(OP_POLL|OP_RNDTX|OP_TXDATA|OP_TXRXPEND); + LMIC.dataBeg = LMIC.dataLen = 0; + reportEventNoUpdate(EV_TXCOMPLETE); + return; + } LMIC.osjob.func = FUNC_ADDR(updataDone); - } + } // end of else (not joining) LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr); LMIC.dndr = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1 LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; LMICbandplan_updateTx(txbeg); - reportEvent(EV_TXSTART); + // limit power to value asked in adr + LMIC.radio_txpow = LMIC.txpow > LMIC.adrTxPow ? LMIC.adrTxPow : LMIC.txpow; + reportEventNoUpdate(EV_TXSTART); os_radio(RADIO_TX); return; } @@ -1920,7 +2633,8 @@ static void engineUpdate (void) { LMIC.rxtime = LMIC.bcnRxtime; if( now - rxtime >= 0 ) { LMIC.osjob.func = FUNC_ADDR(processBeacon); - os_radio(RADIO_RX); + + radioRx(); return; } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); @@ -1936,6 +2650,24 @@ static void engineUpdate (void) { os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); } +// Decide what to do next for the MAC layer of a device. +// Outer part. Safe to call from anywhere; defers if it +// detects a recursive call. +static void engineUpdate (void) { + lmic_engine_update_state_t state; + + state = LMIC.engineUpdateState; + if (state == lmic_EngineUpdateState_idle) { + LMIC.engineUpdateState = lmic_EngineUpdateState_busy; + do { + engineUpdate_inner(); + state = LMIC.engineUpdateState - 1; + LMIC.engineUpdateState = state; + } while (state != lmic_EngineUpdateState_idle); + } else { + LMIC.engineUpdateState = lmic_EngineUpdateState_again; + } +} void LMIC_setAdrMode (bit_t enabled) { LMIC.adrEnabled = enabled ? FCT_ADREN : 0; @@ -1954,7 +2686,10 @@ void LMIC_shutdown (void) { LMIC.opmode |= OP_SHUTDOWN; } - +// reset the LMIC. This is called at startup; the clear of LMIC.osjob +// only works because the LMIC is guaranteed to be zero in that case. +// But it's also called at frame-count rollover; in that case we have +// to ensure that the user callback pointers are not clobbered. void LMIC_reset (void) { EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, e_.eui = MAIN::CDEV->getEui(), @@ -1962,20 +2697,28 @@ void LMIC_reset (void) { os_radio(RADIO_RST); os_clearCallback(&LMIC.osjob); - os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC)); - LMIC.devaddr = 0; + // save callback info, clear LMIC, restore. + do { + lmic_client_data_t client = LMIC.client; + + os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC)); + + LMIC.client = client; + } while (0); + + // LMIC.devaddr = 0; // true from os_clearMem(). LMIC.devNonce = os_getRndU2(); LMIC.opmode = OP_NONE; LMIC.errcr = CR_4_5; LMIC.adrEnabled = FCT_ADREN; - LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept - LMIC.dn2Freq = FREQ_DNW2; // ditto + resetJoinParams(); LMIC.rxDelay = DELAY_DNW1; #if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; // defaults for ping LMIC.ping.dr = DR_PING; // ditto LMIC.ping.intvExp = 0xFF; #endif // !DISABLE_PING + LMICbandplan_resetDefaultChannels(); DO_DEVDB(LMIC.devaddr, devaddr); DO_DEVDB(LMIC.devNonce, devNonce); @@ -2001,8 +2744,24 @@ void LMIC_init (void) { void LMIC_clrTxData (void) { - LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL); + u2_t opmode = LMIC.opmode; + bit_t const txActive = opmode & OP_TXDATA; + if (! txActive) { + return; + } LMIC.pendTxLen = 0; + opmode &= ~(OP_TXDATA | OP_POLL); + if (! (opmode & OP_JOINING)) { + // in this case, we are joining, and the TX data + // is just pending. + opmode &= ~(OP_TXRXPEND); + } + + LMIC.opmode = opmode; + + if (txActive) + reportEventNoUpdate(EV_TXCANCELED); + if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING return; os_clearCallback(&LMIC.osjob); @@ -2010,28 +2769,120 @@ void LMIC_clrTxData (void) { engineUpdate(); } +dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) { + if (payloadSize > MAX_LEN_PAYLOAD) { + return dr; + } + + const u1_t frameSize = payloadSize + OFF_DAT_OPTS + 5; + dr_t trialDr, nextDr; + + for (trialDr = dr; ;) { + if (! LMICbandplan_isDataRateFeasible(trialDr)) + break; + u1_t maxSizeThisDr = LMICbandplan_maxFrameLen(trialDr); + if (maxSizeThisDr == 0) { + break; + } else if (frameSize <= maxSizeThisDr) { + // we found one that is feasible! + return trialDr; + } + // try the next DR + nextDr = incDR(trialDr); + if (nextDr == trialDr) + break; + trialDr = nextDr; + } + + // if we get here, we didn't find a working dr. + return dr; +} + +static bit_t isTxPathBusy(void) { + return (LMIC.opmode & (OP_TXDATA|OP_JOINING)) != 0; +} + +static bit_t adjustDrForFrameIfNotBusy(u1_t len) { + if (isTxPathBusy()) { + return 0; + } + dr_t newDr = LMIC_feasibleDataRateForFrame(LMIC.datarate, len); + if (newDr != LMIC.datarate) { + setDrTxpow(DRCHG_FRAMESIZE, newDr, KEEP_TXPOW); + } + return 1; +} void LMIC_setTxData (void) { + adjustDrForFrameIfNotBusy(LMIC.pendTxLen); + LMIC_setTxData_strict(); +} + +void LMIC_setTxData_strict (void) { + LMICOS_logEventUint32(__func__, (LMIC.pendTxPort << 24u) | (LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u)); LMIC.opmode |= OP_TXDATA; - if( (LMIC.opmode & OP_JOINING) == 0 ) - LMIC.txCnt = 0; // cancel any ongoing TX/RX retries + if( (LMIC.opmode & OP_JOINING) == 0 ) { + LMIC.txCnt = 0; // reset the confirmed uplink FSM + LMIC.upRepeatCount = 0; // reset the unconfirmed repeat FSM + } engineUpdate(); } -// -int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { +// send a message, attempting to adjust TX data rate +lmic_tx_error_t LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { + adjustDrForFrameIfNotBusy(dlen); + return LMIC_setTxData2_strict(port, data, dlen, confirmed); +} + +// send a message w/o callback; do not adjust data rate +lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { + if ( LMIC.opmode & OP_TXDATA ) { + // already have a message queued + return LMIC_ERROR_TX_BUSY; + } if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) - return -2; + return LMIC_ERROR_TX_TOO_LARGE; if( data != (xref2u1_t)0 ) os_copyMem(LMIC.pendTxData, data, dlen); LMIC.pendTxConf = confirmed; LMIC.pendTxPort = port; LMIC.pendTxLen = dlen; - LMIC_setTxData(); + LMIC_setTxData_strict(); + if ( (LMIC.opmode & OP_TXDATA) == 0 ) { + if (LMIC.txrxFlags & TXRX_LENERR) { + return LMIC_ERROR_TX_NOT_FEASIBLE; + } else { + // data has already been completed with error for some reason + return LMIC_ERROR_TX_FAILED; + } + } return 0; } +// send a message with callback; try to adjust data rate +lmic_tx_error_t LMIC_sendWithCallback ( + u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, + lmic_txmessage_cb_t *pCb, void *pUserData +) { + adjustDrForFrameIfNotBusy(dlen); + return LMIC_sendWithCallback_strict(port, data, dlen, confirmed, pCb, pUserData); +} + +// send a message with callback; do not adjust datarate +lmic_tx_error_t LMIC_sendWithCallback_strict ( + u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, + lmic_txmessage_cb_t *pCb, void *pUserData +) { + lmic_tx_error_t const result = LMIC_setTxData2_strict(port, data, dlen, confirmed); + if (result == 0) { + LMIC.client.txMessageCb = pCb; + LMIC.client.txMessageUserData = pUserData; + } + return result; +} + + // Send a payload-less message to signal device is alive void LMIC_sendAlive (void) { LMIC.opmode |= OP_POLL; @@ -2072,9 +2923,12 @@ void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t LMICbandplan_setSessionInitDefaultChannels(); - LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_UNJOIN|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); LMIC.opmode |= OP_NEXTCHNL; stateJustJoined(); + // transition to the ADR_ACK_DELAY state. + setAdrAckCount(LINK_CHECK_CONT); + DO_DEVDB(LMIC.netid, netid); DO_DEVDB(LMIC.devaddr, devaddr); DO_DEVDB(LMIC.nwkKey, nwkkey); @@ -2100,7 +2954,7 @@ void LMIC_setLinkCheckMode (bit_t enabled) { // allows for +/- 640 at SF7BW250). MAX_CLOCK_ERROR represents +/-100%, // so e.g. for a +/-1% error you would pass MAX_CLOCK_ERROR * 1 / 100. void LMIC_setClockError(u2_t error) { - LMIC.clockError = error; + LMIC.client.clockError = error; } // \brief return the uplink sequence number for the next transmission. @@ -2135,8 +2989,8 @@ void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void * #if LMIC_ENABLE_DeviceTimeReq if (LMIC.txDeviceTimeReqState == lmic_RequestTimeState_idle) { LMIC.txDeviceTimeReqState = lmic_RequestTimeState_tx; - LMIC.pNetworkTimeCb = pCallbackfn; - LMIC.pNetworkTimeUserData = pUserData; + LMIC.client.pNetworkTimeCb = pCallbackfn; + LMIC.client.pNetworkTimeUserData = pUserData; return; } #endif // LMIC_ENABLE_DeviceTimeReq diff --git a/src/lmic/lmic.h b/src/lmic/lmic.h index 331d5a3..d05d2c0 100755 --- a/src/lmic/lmic.h +++ b/src/lmic/lmic.h @@ -103,9 +103,9 @@ extern "C"{ // Arduino LMIC version #define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \ - (((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local)) + (((major) << 24ul) | ((minor) << 16ul) | ((patch) << 8ul) | ((local) << 0ul)) -#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 3, 2, 0) /* v2.3.2 */ +#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(3, 0, 99, 5) /* v3.0.99.5 */ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ (((v) >> 24u) & 0xFFu) @@ -122,14 +122,19 @@ extern "C"{ //! Only For Antenna Tuning Tests ! //#define CFG_TxContinuousMode 1 -enum { MAX_FRAME_LEN = 64 }; //!< Library cap on max frame length +// since this was annouunced 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 + enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames enum { MAX_MISSED_BCNS = 20 }; // threshold for triggering rejoin requests enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this -enum { LINK_CHECK_CONT = 12 , // continue with this after reported dead link - LINK_CHECK_DEAD = 24 , // after this UP frames and no response from NWK assume link is dead - LINK_CHECK_INIT = -12 , // UP frame count until we inc datarate +enum { LINK_CHECK_CONT = 0 , // continue with this after reported dead link + LINK_CHECK_DEAD = 32 , // after this UP frames and no response to ack from NWK assume link is dead (ADR_ACK_DELAY) + LINK_CHECK_UNJOIN_MIN = LINK_CHECK_DEAD + 4, // this is the minimum value of LINK_CHECK_UNJOIN if we parameterize + LINK_CHECK_UNJOIN = LINK_CHECK_DEAD + (3 * 240), // after this many UP frames and no response, switch to join (by default) + LINK_CHECK_INIT = -64 , // UP frame count until we ask for ack (ADR_ACK_LIMIT) LINK_CHECK_OFF =-128 }; // link check disabled enum { TIME_RESYNC = 6*128 }; // secs @@ -153,14 +158,27 @@ struct band_t { }; TYPEDEF_xref2band_t; //!< \internal +struct lmic_saved_adr_state_s { + u4_t channelFreq[MAX_CHANNELS]; + u2_t channelMap; +}; + #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 activeChannels125khz; + u2_t activeChannels500khz; +}; + #endif // ========================================================================== +typedef struct lmic_saved_adr_state_s lmic_saved_adr_state_t; + // Keep in sync with evdefs.hpp::drChange -enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD }; +enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD, DRCHG_FRAMESIZE }; enum { KEEP_TXPOW = -128 }; @@ -189,14 +207,14 @@ enum { BCN_NONE = 0x00, //!< No beacon received //! Information about the last and previous beacons. struct bcninfo_t { ostime_t txtime; //!< Time when the beacon was sent + u4_t time; //!< GPS time in seconds of last beacon (received or surrogate) + s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) + s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) s1_t rssi; //!< Adjusted RSSI value of last received beacon s1_t snr; //!< Scaled SNR value of last received beacon u1_t flags; //!< Last beacon reception and tracking states. See BCN_* values. - u4_t time; //!< GPS time in seconds of last beacon (received or surrogate) // u1_t info; //!< Info field of last beacon (valid only if BCN_FULL set) - s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) - s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) }; #endif // !DISABLE_BEACONS @@ -220,29 +238,99 @@ enum { OP_NONE = 0x0000, OP_NEXTCHNL = 0x0800, // find a new channel OP_LINKDEAD = 0x1000, // link was reported as dead OP_TESTMODE = 0x2000, // developer test mode + OP_UNJOIN = 0x4000, // unjoin and rejoin on next engineUpdate(). }; // TX-RX transaction flags - report back to user enum { TXRX_ACK = 0x80, // confirmed UP frame was acked TXRX_NACK = 0x40, // confirmed UP frame was not acked TXRX_NOPORT = 0x20, // set if a frame with a port was RXed, clr if no frame/no port TXRX_PORT = 0x10, // set if a frame with a port was RXed, LMIC.frame[LMIC.dataBeg-1] => port - TXRX_DNW1 = 0x01, // received in 1st DN slot + TXRX_LENERR = 0x08, // set if frame was discarded due to length error. + TXRX_PING = 0x04, // received in a scheduled RX slot TXRX_DNW2 = 0x02, // received in 2dn DN slot - TXRX_PING = 0x04 }; // received in a scheduled RX slot + TXRX_DNW1 = 0x01, // received in 1st DN slot +}; + // Event types for event callback enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING, EV_JOINED, EV_RFU1, EV_JOIN_FAILED, EV_REJOIN_FAILED, EV_TXCOMPLETE, EV_LOST_TSYNC, EV_RESET, EV_RXCOMPLETE, EV_LINK_DEAD, EV_LINK_ALIVE, EV_SCAN_FOUND, - EV_TXSTART }; + EV_TXSTART, EV_TXCANCELED, EV_RXSTART, EV_JOIN_TXCOMPLETE }; typedef enum _ev_t ev_t; +// this macro can be used to initalize a normal table of event strings +#define LMIC_EVENT_NAME_TABLE__INIT \ + "<>", \ + "EV_SCAN_TIMEOUT", "EV_BEACON_FOUND", \ + "EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING", \ + "EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED", \ + "EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET", \ + "EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE", "EV_SCAN_FOUND", \ + "EV_TXSTART", "EV_TXCANCELED", "EV_RXSTART", "EV_JOIN_TXCOMPLETE" + +// if working on an AVR (or worried about it), you can use this multi-zero +// string and put this in a single const F() string. Index through this +// counting up from 0, until you get to the entry you want or to an +// entry that begins with a \0. +#define LMIC_EVENT_NAME_MULTISZ__INIT \ + "<>\0" \ + "EV_SCAN_TIMEOUT\0" "EV_BEACON_FOUND\0" \ + "EV_BEACON_MISSED\0" "EV_BEACON_TRACKED\0" "EV_JOINING\0" \ + "EV_JOINED\0" "EV_RFU1\0" "EV_JOIN_FAILED\0" "EV_REJOIN_FAILED\0" \ + "EV_TXCOMPLETE\0" "EV_LOST_TSYNC\0" "EV_RESET\0" \ + "EV_RXCOMPLETE\0" "EV_LINK_DEAD\0" "EV_LINK_ALIVE\0" "EV_SCAN_FOUND\0" \ + "EV_TXSTART\0" "EV_TXCANCELED\0" "EV_RXSTART\0" "EV_JOIN_TXCOMPLETE\0" + +enum { + LMIC_ERROR_SUCCESS = 0, + LMIC_ERROR_TX_BUSY = -1, + LMIC_ERROR_TX_TOO_LARGE = -2, + LMIC_ERROR_TX_NOT_FEASIBLE = -3, + LMIC_ERROR_TX_FAILED = -4, +}; + +typedef int lmic_tx_error_t; + +#define LMIC_ERROR_NAME__INIT \ + "LMIC_ERROR_SUCCESS", \ + "LMIC_ERROR_TX_BUSY", \ + "LMIC_ERROR_TX_TOO_LARGE", \ + "LMIC_ERROR_TX_NOT_FEASIBLE", \ + "LMIC_ERROR_TX_FAILED" + +#define LMIC_ERROR_NAME_MULTISZ__INIT \ + "LMIC_ERROR_SUCCESS\0" \ + "LMIC_ERROR_TX_BUSY\0" \ + "LMIC_ERROR_TX_TOO_LARGE\0" \ + "LMIC_ERROR_TX_NOT_FEASIBLE\0" \ + "LMIC_ERROR_TX_FAILED" + +enum { + LMIC_BEACON_ERROR_INVALID = -2, + LMIC_BEACON_ERROR_WRONG_NETWORK = -1, + LMIC_BEACON_ERROR_SUCCESS_PARTIAL = 0, + LMIC_BEACON_ERROR_SUCCESS_FULL = 1, +}; + +typedef s1_t lmic_beacon_error_t; + +static inline bit_t LMIC_BEACON_SUCCESSFUL(lmic_beacon_error_t e) { + return e < 0; +} + enum { // This value represents 100% error in LMIC.clockError MAX_CLOCK_ERROR = 65536, }; +// callbacks for client alerts. +// types and functions are always defined, to reduce #ifs in example code and libraries. +typedef void LMIC_ABI_STD lmic_rxmessage_cb_t(void *pUserData, uint8_t port, const uint8_t *pMessage, size_t nMessage); +typedef void LMIC_ABI_STD lmic_txmessage_cb_t(void *pUserData, int fSuccess); +typedef void LMIC_ABI_STD lmic_event_cb_t(void *pUserData, ev_t e); + // network time request callback function // defined unconditionally, because APIs and types can't change based on config. // This is called when a time-request succeeds or when we get a downlink @@ -272,29 +360,123 @@ enum lmic_request_time_state_e { typedef u1_t lmic_request_time_state_t; +enum lmic_engine_update_state_e { + lmic_EngineUpdateState_idle = 0, // engineUpdate is idle. + lmic_EngineUpdateState_busy = 1, // engineUpdate is busy, but has not been reentered. + lmic_EngineUpdateState_again = 2, // engineUpdate is busy, and has to be evaluated again. +}; + +typedef u1_t lmic_engine_update_state_t; + +/* + +Structure: lmic_client_data_t + +Function: + Holds LMIC client data that must live through LMIC_reset(). + +Description: + There are a variety of client registration linkage items that + must live through LMIC_reset(), because LMIC_reset() is called + at frame rollover time. We group them together into a structure + to make copies easy. + +*/ + +//! abstract type for collection of client data that survives LMIC_reset(). +typedef struct lmic_client_data_s lmic_client_data_t; + +//! contents of lmic_client_data_t +struct lmic_client_data_s { + + /* pointer-width things come first */ +#if LMIC_ENABLE_DeviceTimeReq + lmic_request_network_time_cb_t *pNetworkTimeCb; //! call-back routine for network time + void *pNetworkTimeUserData; //! call-back data for network time. +#endif + +#if LMIC_ENABLE_user_events + lmic_event_cb_t *eventCb; //! user-supplied callback function for events. + void *eventUserData; //! data for eventCb + lmic_rxmessage_cb_t *rxMessageCb; //! user-supplied message-received callback + void *rxMessageUserData; //! data for rxMessageCb + lmic_txmessage_cb_t *txMessageCb; //! transmit-complete message handler; reset on each tx complete. + void *txMessageUserData; //! data for txMessageCb. +#endif // LMIC_ENABLE_user_events + + /* next we have things that are (u)int32_t */ + /* none at the moment */ + + /* next we have things that are (u)int16_t */ + + u2_t clockError; //! Inaccuracy in the clock. CLOCK_ERROR_MAX represents +/-100% error + + /* finally, things that are (u)int8_t */ + /* none at the moment */ +}; + +/* + +Structure: lmic_t + +Function: + Provides the instance data for the LMIC. + +*/ + struct lmic_t { + // client setup data, survives LMIC_reset(). + lmic_client_data_t client; + + // the OS job object. pointer alignment. + osjob_t osjob; + +#if !defined(DISABLE_BEACONS) + bcninfo_t bcninfo; // Last received beacon info +#endif + +#if !defined(DISABLE_PING) + rxsched_t ping; // pingable setup +#endif + + /* (u)int32_t things */ + // Radio settings TX/RX (also accessed by HAL) ostime_t txend; ostime_t rxtime; // LBT info ostime_t lbt_ticks; // ticks to listen - s1_t lbt_dbmax; // max permissible dB on our channel (eg -80) u4_t freq; - s1_t rssi; - s1_t snr; // LMIC.snr is SNR times 4 - rps_t rps; - u1_t rxsyms; - u1_t dndr; - s1_t txpow; // dBm - osjob_t osjob; + ostime_t globalDutyAvail; // time device can send again - // Channel scheduling + u4_t netid; // current network id (~0 - none) + devaddr_t devaddr; + u4_t seqnoDn; // device level down stream seqno + u4_t seqnoUp; + u4_t dn2Freq; + +#if !defined(DISABLE_BEACONS) + ostime_t bcnRxtime; +#endif + +#if LMIC_ENABLE_DeviceTimeReq + // put here for alignment, to reduce RAM use. + ostime_t localDeviceTime; // the LMIC.txend value for last DeviceTimeAns + lmic_gpstime_t netDeviceTime; // the netDeviceTime for lastDeviceTimeAns + // zero ==> not valid. +#endif // LMIC_ENABLE_DeviceTimeReq + + // Channel scheduling -- very much private #if CFG_LMIC_EU_like band_t bands[MAX_BANDS]; u4_t channelFreq[MAX_CHANNELS]; +#if !defined(DISABLE_MCMD_DlChannelReq) + u4_t channelDlFreq[MAX_CHANNELS]; +#endif + // bit map of enabled datarates for each channel u2_t channelDrMap[MAX_CHANNELS]; u2_t channelMap; #elif CFG_LMIC_US_like @@ -304,66 +486,70 @@ struct lmic_t { u2_t activeChannels125khz; u2_t activeChannels500khz; #endif + + /* (u)int16_t things */ + rps_t rps; // radio parameter selections: SF, BW, CodingRate, NoCrc, implicit hdr + u2_t opmode; // engineUpdate() operating mode flags + u2_t devNonce; // last generated nonce + + s2_t adrAckReq; // counter for link integrity tracking (LINK_CHECK_OFF=off) + +#if !defined(DISABLE_BEACONS) + s2_t drift; // last measured drift + s2_t lastDriftDiff; + s2_t maxDriftDiff; +#endif + + /* (u)int8_t things */ + lmic_engine_update_state_t engineUpdateState; // state of the engineUpdate() evaluator. + s1_t rssi; + s1_t snr; // LMIC.snr is SNR times 4 + u1_t rxsyms; + u1_t dndr; + s1_t txpow; // transmit dBm (administrative) + s1_t radio_txpow; // the radio driver's copy of txpow, in dB limited by adrTxPow, and + // also adjusted for EIRP/antenna gain considerations. + // This is just the radio's idea of power. So if you are + // controlling EIRP, and you have 3 dB antenna gain, this + // needs to reduced by 3 dB. + s1_t lbt_dbmax; // max permissible dB on our channel (eg -80) + u1_t txChnl; // channel for next TX u1_t globalDutyRate; // max rate: 1/2^k - ostime_t globalDutyAvail; // time device can send again - u4_t netid; // current network id (~0 - none) - u2_t opmode; u1_t upRepeat; // configured up repeat s1_t adrTxPow; // ADR adjusted TX power u1_t datarate; // current data rate u1_t errcr; // error coding rate (used for TX only) u1_t rejoinCnt; // adjustment for rejoin datarate -#if !defined(DISABLE_BEACONS) - s2_t drift; // last measured drift - s2_t lastDriftDiff; - s2_t maxDriftDiff; -#endif - u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX - // represents +/-100% error + u1_t upRepeatCount; // current up-repeat + bit_t initBandplanAfterReset; // cleared by LMIC_reset(), set by first join. See issue #244 u1_t pendTxPort; u1_t pendTxConf; // confirmed data - u1_t pendTxLen; // +0x80 = confirmed + u1_t pendTxLen; // count of bytes in pendTxData. u1_t pendTxData[MAX_LEN_PAYLOAD]; - u2_t devNonce; // last generated nonce + u1_t pendMacLen; // number of bytes of pending Mac response data + bit_t pendMacPiggyback; // received on port 0 or piggyback? + // response data if piggybacked + u1_t pendMacData[LWAN_FCtrl_FOptsLen_MAX]; + u1_t nwkKey[16]; // network session key u1_t artKey[16]; // application router session key - devaddr_t devaddr; - u4_t seqnoDn; // device level down stream seqno - u4_t seqnoUp; -#if LMIC_ENABLE_DeviceTimeReq - // put here for alignment, to reduce RAM use. - ostime_t localDeviceTime; // the LMIC.txend value for last DeviceTimeAns - lmic_gpstime_t netDeviceTime; // the netDeviceTime for lastDeviceTimeAns - // zero ==> not valid. - lmic_request_network_time_cb_t *pNetworkTimeCb; // call-back routine - void *pNetworkTimeUserData; // call-back data -#endif // LMIC_ENABLE_DeviceTimeReq u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0 - s1_t adrAckReq; // counter until we reset data rate (0=off) + u1_t lastDnConf; // downlink with seqnoDn-1 requested confirmation u1_t adrChanged; u1_t rxDelay; // Rx delay after TX u1_t margin; - bit_t ladrAns; // link adr adapt answer pending - bit_t devsAns; // device status answer pending s1_t devAnsMargin; // SNR value between -32 and 31 (inclusive) for the last successfully received DevStatusReq command u1_t adrEnabled; u1_t moreData; // NWK has more data pending -#if !defined(DISABLE_MCMD_DCAP_REQ) - bit_t dutyCapAns; // have to ACK duty cycle settings -#endif -#if !defined(DISABLE_MCMD_SNCH_REQ) - u1_t snchAns; // answer set new channel -#endif #if LMIC_ENABLE_TxParamSetupReq - bit_t txParamSetupAns; // transmit setup answer pending. u1_t txParam; // the saved TX param byte. #endif #if LMIC_ENABLE_DeviceTimeReq @@ -376,23 +562,21 @@ struct lmic_t { // 2nd RX window (after up stream) u1_t dn2Dr; - u4_t dn2Freq; -#if !defined(DISABLE_MCMD_DN2P_SET) +#if !defined(DISABLE_MCMD_RXParamSetupReq) u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs #endif +#if !defined(DISABLE_MCMD_DlChannelReq) + u1_t macDlChannelAns; // 0 ==> no answer pending, 0x80+ACK bits +#endif +#if !defined(DISABLE_MCMD_RXTimingSetupReq) + bit_t macRxTimingSetupAns; // 0 ==> no answer pend, non-zero inserts response. +#endif // Class B state #if !defined(DISABLE_BEACONS) u1_t missedBcns; // unable to track last N beacons u1_t bcninfoTries; // how often to try (scan mode only) #endif -#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) - u1_t pingSetAns; // answer set cmd and ACK bits -#endif -#if !defined(DISABLE_PING) - rxsched_t ping; // pingable setup -#endif - // Public part of MAC state u1_t txCnt; u1_t txrxFlags; // transaction flags (TX-RX combo) @@ -403,11 +587,10 @@ struct lmic_t { #if !defined(DISABLE_BEACONS) u1_t bcnChnl; u1_t bcnRxsyms; // - ostime_t bcnRxtime; - bcninfo_t bcninfo; // Last received beacon info #endif u1_t noRXIQinversion; + u1_t saveIrqFlags; // last LoRa IRQ flags }; //! \var struct lmic_t LMIC @@ -418,16 +601,20 @@ DECLARE_LMIC; //!< \internal #define DR_RANGE_MAP(drlo,drhi) (((u2_t)0xFFFF<<(drlo)) & ((u2_t)0xFFFF>>(15-(drhi)))) bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap); bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band); -void LMIC_disableChannel (u1_t channel); -void LMIC_enableSubBand(u1_t band); -void LMIC_enableChannel(u1_t channel); -void LMIC_disableSubBand(u1_t band); -void LMIC_selectSubBand(u1_t band); +bit_t LMIC_disableChannel (u1_t channel); +bit_t LMIC_enableSubBand(u1_t band); +bit_t LMIC_enableChannel(u1_t channel); +bit_t LMIC_disableSubBand(u1_t band); +bit_t LMIC_selectSubBand(u1_t band); void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) + #if !defined(DISABLE_JOIN) bit_t LMIC_startJoining (void); +void LMIC_tryRejoin (void); +void LMIC_unjoin (void); +void LMIC_unjoinAndRejoin (void); #endif void LMIC_shutdown (void); @@ -435,7 +622,11 @@ void LMIC_init (void); void LMIC_reset (void); void LMIC_clrTxData (void); void LMIC_setTxData (void); -int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); +void LMIC_setTxData_strict(void); +lmic_tx_error_t LMIC_setTxData2(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); +lmic_tx_error_t LMIC_setTxData2_strict(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); +lmic_tx_error_t LMIC_sendWithCallback(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData); +lmic_tx_error_t LMIC_sendWithCallback_strict(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData); void LMIC_sendAlive (void); #if !defined(DISABLE_BEACONS) @@ -447,9 +638,6 @@ void LMIC_disableTracking (void); void LMIC_stopPingable (void); void LMIC_setPingable (u1_t intvExp); #endif -#if !defined(DISABLE_JOIN) -void LMIC_tryRejoin (void); -#endif void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey); void LMIC_setLinkCheckMode (bit_t enabled); @@ -462,11 +650,26 @@ void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xre void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void *pUserData); 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); + +// APIs for client half of compliance. +typedef u1_t lmic_compliance_rx_action_t; + +enum lmic_compliance_rx_action_e { + LMIC_COMPLIANCE_RX_ACTION_PROCESS = 0, // process this message normally + LMIC_COMPLIANCE_RX_ACTION_START, // enter compliance mode, discard this message + LMIC_COMPLIANCE_RX_ACTION_IGNORE, // continue in compliance mode, discard this message + LMIC_COMPLIANCE_RX_ACTION_END // exit compliance mode, discard this message +}; + +lmic_compliance_rx_action_t LMIC_complianceRxMessage(u1_t port, const u1_t *pMessage, size_t nMessage); + // Declare onEvent() function, to make sure any definition will have the // C conventions, even when in a C++ file. +#if LMIC_ENABLE_onEvent DECL_ON_LMIC_EVENT; - - +#endif /* LMIC_ENABLE_onEvent */ // Special APIs - for development or testing // !!!See implementation for caveats!!! diff --git a/src/lmic/lmic_as923.c b/src/lmic/lmic_as923.c index 7bdce7d..f46f50b 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,19 +51,19 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { }; // see table in 2.7.6 -- this assumes UplinkDwellTime = 0. -static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = { +static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = { 59+5, // [0] 59+5, // [1] 59+5, // [2] 123+5, // [3] - 230+5, // [4] - 230+5, // [5] - 230+5, // [6] - 230+5 // [7] + 250+5, // [4] + 250+5, // [5] + 250+5, // [6] + 250+5 // [7] }; // see table in 2.7.6 -- this assumes UplinkDwellTime = 1. -static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = { +static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = { 0, // [0] 0, // [1] 19+5, // [2] @@ -74,18 +74,20 @@ static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = { 250+5 // [7] }; -static uint8_t +static uint8_t LMICas923_getUplinkDwellBit(uint8_t mcmd_txparam) { - LMIC_API_PARAMETER(mcmd_txparam); + if (mcmd_txparam == 0xFF) + return AS923_INITIAL_TxParam_UplinkDwellTime; - return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0; + return (mcmd_txparam & MCMD_TxParam_TxDWELL_MASK) != 0; } -static uint8_t +static uint8_t LMICas923_getDownlinkDwellBit(uint8_t mcmd_txparam) { - LMIC_API_PARAMETER(mcmd_txparam); + if (mcmd_txparam == 0xFF) + return AS923_INITIAL_TxParam_DownlinkDwellTime; - return (LMIC.txParam & MCMD_TxParam_RxDWELL_MASK) != 0; + return (mcmd_txparam & MCMD_TxParam_RxDWELL_MASK) != 0; } uint8_t LMICas923_maxFrameLen(uint8_t dr) { @@ -95,7 +97,7 @@ uint8_t LMICas923_maxFrameLen(uint8_t dr) { else return TABLE_GET_U1(maxFrameLens_dwell0, dr); } else { - return 0xFF; + return 0; } } @@ -110,7 +112,6 @@ static CONST_TABLE(s1_t, TXPOWLEVELS)[] = { -10, // [5]: MaxEIRP - 10dB -12, // [6]: MaxEIRP - 12dB -14, // [7]: MaxEIRP - 14dB - 0, 0, 0, 0, 0, 0, 0, 0 }; // from LoRaWAN 5.8: mapping from txParam to MaxEIRP @@ -119,6 +120,7 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = { }; static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) { + // if uninitialized, return default. if (mcmd_txparam == 0xFF) return AS923_TX_EIRP_MAX_DBM; else @@ -127,18 +129,23 @@ static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) { (mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >> MCMD_TxParam_MaxEIRP_SHIFT ); -} +} -// translate from an encoded power to an actual power using -// the maxeirp setting. +// translate from an encoded power to an actual power using +// the maxeirp setting; return -128 if not legal. int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1) { - s1_t const adj = - TABLE_GET_S1( - TXPOWLEVELS, - (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT - ); - - return adj; + uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT; + if (pindex < LENOF_TABLE(TXPOWLEVELS)) { + s1_t const adj = + TABLE_GET_S1( + TXPOWLEVELS, + pindex + ); + + return LMICas923_getMaxEIRP(LMIC.txParam) + adj; + } else { + return -128; + } } // only used in this module, but used by variant macro dr2hsym(). @@ -162,7 +169,7 @@ ostime_t LMICas923_dr2hsym(uint8_t dr) { enum { NUM_DEFAULT_CHANNELS = 2 }; static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = { // Default operational frequencies - AS923_F1 | BAND_CENTI, + AS923_F1 | BAND_CENTI, AS923_F2 | BAND_CENTI, }; @@ -171,6 +178,9 @@ void LMICas923_initDefaultChannels(bit_t join) { LMIC_API_PARAMETER(join); os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); +#if !defined(DISABLE_MCMD_DlChannelReq) + os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq)); +#endif // !DISABLE_MCMD_DlChannelReq os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); @@ -217,6 +227,18 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { } 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; + } + bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) return 0; if (band == -1) { @@ -226,10 +248,13 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { freq = (freq&~3) | band; } LMIC.channelFreq[chidx] = freq; - LMIC.channelDrMap[chidx] = - drmap == 0 ? DR_RANGE_MAP(AS923_DR_SF12, AS923_DR_SF7B) + LMIC.channelDrMap[chidx] = + drmap == 0 ? DR_RANGE_MAP(AS923_DR_SF12, AS923_DR_SF7B) : drmap; - LMIC.channelMap |= 1 << chidx; // enabled right away + if (fEnable) + LMIC.channelMap |= 1 << chidx; // enabled right away + else + LMIC.channelMap &= ~(1 << chidx); return 1; } @@ -259,7 +284,9 @@ void LMICas923_setRx1Params(void) { int const txdr = LMIC.dndr; int effective_rx1DrOffset; int candidateDr; - + + LMICeulike_setRx1Freq(); + effective_rx1DrOffset = LMIC.rx1DrOffset; // per section 2.7.7 of regional, lines 1101:1103: switch (effective_rx1DrOffset) { @@ -267,22 +294,22 @@ void LMICas923_setRx1Params(void) { case 7: effective_rx1DrOffset = -2; break; default: /* no change */ break; } - + // per regional 2.2.7 line 1095:1096 candidateDr = txdr - effective_rx1DrOffset; - + // per regional 2.2.7 lines 1097:1100 if (LMICas923_getDownlinkDwellBit(LMIC.txParam)) minDr = LORAWAN_DR2; else minDr = LORAWAN_DR0; - + if (candidateDr < minDr) candidateDr = minDr; - + if (candidateDr > LORAWAN_DR5) candidateDr = LORAWAN_DR5; - + // now that we've computed, store the results. LMIC.dndr = (uint8_t) candidateDr; LMIC.rps = dndr2rps(LMIC.dndr); @@ -334,23 +361,18 @@ ostime_t LMICas923_nextJoinState(void) { } #endif // !DISABLE_JOIN -// txDone handling for FSK. -void -LMICas923_txDoneFSK(ostime_t delay, osjobcb_t func) { - LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); - LMIC.rxsyms = RXLEN_FSK; - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); -} - void LMICas923_initJoinLoop(void) { - LMIC.txParam = 0xFF; + // LMIC.txParam is set to 0xFF by the central code at init time. LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_TX_EIRP_MAX_DBM); } void LMICas923_updateTx(ostime_t txbeg) { u4_t freq = LMIC.channelFreq[LMIC.txChnl]; + u4_t dwellDelay; + u4_t globalDutyDelay; + // Update global/band specific duty cycle stats ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); // Update channel/global duty cycle stats @@ -358,8 +380,18 @@ LMICas923_updateTx(ostime_t txbeg) { LMIC.freq = freq & ~(u4_t)3; LMIC.txpow = LMICas923_getMaxEIRP(LMIC.txParam); band->avail = txbeg + airtime * band->txcap; - if (LMIC.globalDutyRate != 0) - LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate); + dwellDelay = globalDutyDelay = 0; + if (LMIC.globalDutyRate != 0) { + globalDutyDelay = (airtime << LMIC.globalDutyRate); + } + if (LMICas923_getUplinkDwellBit(LMIC.txParam)) { + dwellDelay = AS923_UPLINK_DWELL_TIME_osticks; + } + if (dwellDelay > globalDutyDelay) { + globalDutyDelay = dwellDelay; + } + if (globalDutyDelay != 0) + LMIC.globalDutyAvail = txbeg + globalDutyDelay; } diff --git a/src/lmic/lmic_au921.c b/src/lmic/lmic_au921.c index bd0b629..e12ad8a 100755 --- a/src/lmic/lmic_au921.c +++ b/src/lmic/lmic_au921.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,38 +52,83 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { MAKERPS(SF9 , BW500, CR_4_5, 0, 0), // [11] MAKERPS(SF8 , BW500, CR_4_5, 0, 0), // [12] MAKERPS(SF7 , BW500, CR_4_5, 0, 0), // [13] - ILLEGAL_RPS + ILLEGAL_RPS }; -static CONST_TABLE(u1_t, maxFrameLens)[] = { - 59+5, 59+5, 59+5, 123+5, 230+5, 230+5, 230+5, 255, - 41+5, 117+5, 230+5, 230+5, 230+5, 230+5 }; +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 }; + +static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = { + 0, 0, 19+5, 61+5, 133+5, 250+5, 250+5, 0, + 61+5, 137+5, 250+5, 250+5, 250+5, 250+5 }; + +static bit_t +LMICau921_getUplinkDwellBit() { + // if uninitialized, return default. + if (LMIC.txParam == 0xFF) { + return AU921_INITIAL_TxParam_UplinkDwellTime; + } + return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0; +} uint8_t LMICau921_maxFrameLen(uint8_t dr) { - if (dr < LENOF_TABLE(maxFrameLens)) - return TABLE_GET_U1(maxFrameLens, dr); - else - return 0xFF; + if (LMICau921_getUplinkDwellBit()) { + if (dr < LENOF_TABLE(maxFrameLens_dwell0)) + return TABLE_GET_U1(maxFrameLens_dwell0, dr); + else + return 0; + } else { + if (dr < LENOF_TABLE(maxFrameLens_dwell1)) + return TABLE_GET_U1(maxFrameLens_dwell1, dr); + else + return 0; + } +} + +// from LoRaWAN 5.8: mapping from txParam to MaxEIRP +static CONST_TABLE(s1_t, TXMAXEIRP)[16] = { + 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 +}; + +static int8_t LMICau921_getMaxEIRP(uint8_t mcmd_txparam) { + // if uninitialized, return default. + if (mcmd_txparam == 0xFF) + return AU921_TX_EIRP_MAX_DBM; + else + return TABLE_GET_S1( + TXMAXEIRP, + (mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >> + MCMD_TxParam_MaxEIRP_SHIFT + ); +} + +int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1) { + if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK) + return -128; + else { + return ((s1_t)(LMICau921_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1))); + } } static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { - us2osticksRound(128 << 7), // DR_SF12 + us2osticksRound(128 << 7), // DR_SF12 us2osticksRound(128 << 6), // DR_SF11 us2osticksRound(128 << 5), // DR_SF10 - us2osticksRound(128 << 4), // DR_SF9 - us2osticksRound(128 << 3), // DR_SF8 - us2osticksRound(128 << 2), // DR_SF7 + us2osticksRound(128 << 4), // DR_SF9 + us2osticksRound(128 << 3), // DR_SF8 + us2osticksRound(128 << 2), // DR_SF7 us2osticksRound(128 << 1), // DR_SF8C - us2osticksRound(128 << 0), // ------ + us2osticksRound(128 << 0), // ------ us2osticksRound(128 << 5), // DR_SF12CR - us2osticksRound(128 << 4), // DR_SF11CR - us2osticksRound(128 << 3), // DR_SF10CR + us2osticksRound(128 << 4), // DR_SF11CR + us2osticksRound(128 << 3), // DR_SF10CR us2osticksRound(128 << 2), // DR_SF9CR us2osticksRound(128 << 1), // DR_SF8CR us2osticksRound(128 << 0), // DR_SF7CR }; -// get ostime for symbols based on datarate. This is not like us915, +// get ostime for symbols based on datarate. This is not like us915, // becuase the times don't match between the upper half and lower half // of the table. ostime_t LMICau921_dr2hsym(uint8_t dr) { @@ -109,9 +154,11 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { return 0; // all channels are hardwired. } -void LMIC_disableChannel(u1_t channel) { +bit_t LMIC_disableChannel(u1_t channel) { + bit_t result = 0; if (channel < 72) { if (ENABLED_CHANNEL(channel)) { + result = 1; if (IS_CHANNEL_125khz(channel)) LMIC.activeChannels125khz--; else if (IS_CHANNEL_500khz(channel)) @@ -119,11 +166,14 @@ void LMIC_disableChannel(u1_t channel) { } LMIC.channelMap[channel >> 4] &= ~(1 << (channel & 0xF)); } + return result; } -void LMIC_enableChannel(u1_t channel) { +bit_t LMIC_enableChannel(u1_t channel) { + bit_t result = 0; if (channel < 72) { if (!ENABLED_CHANNEL(channel)) { + result = 1; if (IS_CHANNEL_125khz(channel)) LMIC.activeChannels125khz++; else if (IS_CHANNEL_500khz(channel)) @@ -131,47 +181,57 @@ void LMIC_enableChannel(u1_t channel) { } LMIC.channelMap[channel >> 4] |= (1 << (channel & 0xF)); } + return result; } -void LMIC_enableSubBand(u1_t band) { +bit_t LMIC_enableSubBand(u1_t band) { ASSERT(band < 8); u1_t start = band * 8; u1_t end = start + 8; + bit_t result = 0; // enable all eight 125 kHz channels in this subband for (int channel = start; channel < end; ++channel) - LMIC_enableChannel(channel); + result |= LMIC_enableChannel(channel); // there's a single 500 kHz channel associated with // each group of 8 125 kHz channels. Enable it, too. - LMIC_enableChannel(64 + band); + result |= LMIC_enableChannel(64 + band); + return result; } -void LMIC_disableSubBand(u1_t band) { + +bit_t LMIC_disableSubBand(u1_t band) { ASSERT(band < 8); u1_t start = band * 8; u1_t end = start + 8; + bit_t result = 0; // disable all eight 125 kHz channels in this subband for (int channel = start; channel < end; ++channel) - LMIC_disableChannel(channel); + result |= LMIC_disableChannel(channel); // there's a single 500 kHz channel associated with // each group of 8 125 kHz channels. Disable it, too. - LMIC_disableChannel(64 + band); + result |= LMIC_disableChannel(64 + band); + return result; } -void LMIC_selectSubBand(u1_t band) { + +bit_t LMIC_selectSubBand(u1_t band) { + bit_t result = 0; + ASSERT(band < 8); for (int b = 0; b<8; ++b) { if (band == b) - LMIC_enableSubBand(b); + result |= LMIC_enableSubBand(b); else - LMIC_disableSubBand(b); + result |= LMIC_disableSubBand(b); } + return result; } void LMICau921_updateTx(ostime_t txbeg) { u1_t chnl = LMIC.txChnl; - LMIC.txpow = AU921_TX_EIRP_MAX_DBM; + LMIC.txpow = LMICau921_getMaxEIRP(LMIC.txParam); if (chnl < 64) { LMIC.freq = AU921_125kHz_UPFBASE + chnl*AU921_125kHz_UPFSTEP; } else { @@ -179,10 +239,23 @@ void LMICau921_updateTx(ostime_t txbeg) { LMIC.freq = AU921_500kHz_UPFBASE + (chnl - 64)*AU921_500kHz_UPFSTEP; } - // Update global duty cycle stats + // Update global duty cycle stat and deal with dwell time. + u4_t dwellDelay; + u4_t globalDutyDelay; + dwellDelay = globalDutyDelay = 0; + if (LMIC.globalDutyRate != 0) { ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); - LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate); + globalDutyDelay = txbeg + (airtime << LMIC.globalDutyRate); + } + if (LMICau921_getUplinkDwellBit(LMIC.txParam)) { + dwellDelay = AU921_UPLINK_DWELL_TIME_osticks; + } + if (dwellDelay > globalDutyDelay) { + globalDutyDelay = dwellDelay; + } + if (globalDutyDelay != 0) { + LMIC.globalDutyAvail = txbeg + globalDutyDelay; } } @@ -213,6 +286,14 @@ void LMICau921_setRx1Params(void) { LMIC.rps = dndr2rps(LMIC.dndr); } +void LMICau921_initJoinLoop(void) { + // LMIC.txParam is set to 0xFF by the central code at init time. + LMICuslike_initJoinLoop(); + + // initialize the adrTxPower. + LMIC.adrTxPow = LMICau921_getMaxEIRP(LMIC.txParam); // dBm + +} // // END: AU921 related stuff diff --git a/src/lmic/lmic_bandplan.h b/src/lmic/lmic_bandplan.h index 0c3c503..74efc61 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,8 @@ # include "lmic_bandplan_au921.h" #elif defined(CFG_as923) # include "lmic_bandplan_as923.h" +#elif defined(CFG_kr920) +# include "lmic_bandplan_kr920.h" #elif defined(CFG_in866) # include "lmic_bandplan_in866.h" #else @@ -52,8 +54,8 @@ # error "DNW2_SAFETY_ZONE not defined by bandplan" #endif -#ifndef maxFrameLen -# error "maxFrameLen() not defined by bandplan" +#ifndef LMICbandplan_maxFrameLen +# error "LMICbandplan_maxFrameLen() not defined by bandplan" #endif #ifndef pow2dBm @@ -104,6 +106,10 @@ # error "LMICbandplan_setBcnRxParams() not defined by bandplan" #endif +#if !defined(LMICbandplan_canMapChannels) +# error "LMICbandplan_canMapChannels() not defined by bandplan" +#endif + #if !defined(LMICbandplan_mapChannels) # error "LMICbandplan_mapChannels() not defined by bandplan" #endif @@ -143,16 +149,65 @@ #if !defined(LMICbandplan_init) # error "LMICbandplan_init() not defined by bandplan" #endif + +#if !defined(LMICbandplan_saveAdrState) +# error "LMICbandplan_saveAdrState() not defined by bandplan" +#endif + +#if !defined(LMICbandplan_compareAdrState) +# error "LMICbandplan_compareAdrState() not defined by bandplan" +#endif + +#if !defined(LMICbandplan_restoreAdrState) +# error "LMICbandplan_restoreAdrState() not defined by bandplan" +#endif + +#if !defined(LMICbandplan_isDataRateFeasible) +# error "LMICbandplan_isDataRateFeasible() not defined by bandplan" +#endif + // // Things common to lmic.c code // +#define LMICbandplan_MINRX_SYMS_LoRa_ClassA 6 +#define LMICbandplan_RX_ERROR_ABS_osticks ms2osticks(10) + +// Semtech inherently (by calculating in ms and taking ceilings) +// rounds up to the next higher ms. It's a lot easier for us +// to just add margin for things like hardware ramp-up time +// and clock calibration when running from the LSE and HSI +// clocks on an STM32. +#define LMICbandplan_RX_EXTRA_MARGIN_osticks us2osticks(2000) + +// probably this should be the same as the Class-A value, but +// we have not the means to thoroughly test this. This is the +// number of rxsyms used in the computations for ping and beacon +// windows. +#define LMICbandplan_MINRX_SYMS_LoRa_ClassB 5 + +#define LMICbandplan_PAMBL_SYMS 8 +#define LMICbandplan_PAMBL_FSK 5 +#define LMICbandplan_PRERX_FSK 1 +#define LMICbandplan_RXLEN_FSK (1+5+2) + +// Legacy names #if !defined(MINRX_SYMS) -#define MINRX_SYMS 5 +# define MINRX_SYMS LMICbandplan_MINRX_SYMS_LoRa_ClassB #endif // !defined(MINRX_SYMS) -#define PAMBL_SYMS 8 -#define PAMBL_FSK 5 -#define PRERX_FSK 1 -#define RXLEN_FSK (1+5+2) +#define PAMBL_SYMS LMICbandplan_PAMBL_SYMS +#define PAMBL_FSK LMICbandplan_PAMBL_FSK +#define PRERX_FSK LMICbandplan_PRERX_FSK +#define RXLEN_FSK LMICbandplan_RXLEN_FSK + +// this is regional, but so far all regions are the same +#if !defined(LMICbandplan_MAX_FCNT_GAP) +# define LMICbandplan_MAX_FCNT_GAP 16384 +#endif // !defined LWAN_MAX_FCNT_GAP + +// this is probably regional, but for now default can be the same +#if !defined(LMICbandplan_TX_RECOVERY_ms) +# define LMICbandplan_TX_RECOVERY_ms 500 +#endif #define BCN_INTV_osticks sec2osticks(BCN_INTV_sec) #define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms) @@ -171,5 +226,7 @@ // internal APIs 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); +ostime_t LMICcore_RxWindowOffset(ostime_t hsym, u1_t rxsyms_in); #endif // _lmic_bandplan_h_ diff --git a/src/lmic/lmic_bandplan_as923.h b/src/lmic/lmic_bandplan_as923.h index 50017f2..16f4518 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,15 +26,17 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _lmic_as923_h_ -# define _lmic_as923_h_ +#ifndef _lmic_bandplan_as923_h_ +# define _lmic_bandplan_as923_h_ #ifndef _lmic_eu_like_h_ # include "lmic_eu_like.h" #endif +// return maximum frame length (including PHY header) for this data rate (as923); 0 --> not valid dr. uint8_t LMICas923_maxFrameLen(uint8_t dr); -#define maxFrameLen(dr) LMICas923_maxFrameLen(dr) +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICas923_maxFrameLen(dr) int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1); #define pow2dBm(mcmd_ladr_p1) LMICas923_pow2dBm(mcmd_ladr_p1) @@ -70,13 +72,7 @@ void LMICas923_init(void); // override default for LMICbandplan_isFSK() #undef LMICbandplan_isFSK -#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == AS923_DR_FSK) - -// txDone handling for FSK. -void -LMICas923_txDoneFSK(ostime_t delay, osjobcb_t func); - -#define LMICbandplan_txDoneFsk(delay, func) LMICas923_txDoneFSK(delay, func) +#define LMICbandplan_isFSK() (/* RX datarate */LMIC.dndr == AS923_DR_FSK) #define LMICbandplan_getInitialDrJoin() (AS923_DR_SF10) @@ -112,4 +108,4 @@ void LMICas923_updateTx(ostime_t txbeg); ostime_t LMICas923_nextJoinTime(ostime_t now); #define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now) -#endif // _lmic_as923_h_ +#endif // _lmic_bandplan_as923_h_ diff --git a/src/lmic/lmic_bandplan_au921.h b/src/lmic/lmic_bandplan_au921.h index f173187..16d7c88 100755 --- a/src/lmic/lmic_bandplan_au921.h +++ b/src/lmic/lmic_bandplan_au921.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,27 +26,33 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _lmic_au921_h_ -# define _lmic_au921_h_ +#ifndef _lmic_bandplan_au921_h_ +# define _lmic_bandplan_au921_h_ // preconditions for lmic_us_like.h -#define LMICuslike_getFirst500kHzDR() (AU921_DR_SF8C) - +#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6) +#define LMICuslike_getJoin125kHzDR() (LORAWAN_DR2) #ifndef _lmic_us_like_h_ # include "lmic_us_like.h" #endif +// return maximum frame length (including PHY header) for this data rate (au921); 0 --> not valid dr. uint8_t LMICau921_maxFrameLen(uint8_t dr); -#define maxFrameLen(dr) LMICau921_maxFrameLen(dr) +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICau921_maxFrameLen(dr) -#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1))) +int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1); +#define pow2dBm(mcmd_ladr_p1) LMICau921_pow2dbm(mcmd_ladr_p1) ostime_t LMICau921_dr2hsym(uint8_t dr); #define dr2hsym(dr) LMICau921_dr2hsym(dr) -#define LMICbandplan_getInitialDrJoin() (EU868_DR_SF7) +#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2) + +void LMICau921_initJoinLoop(void); +#define LMICbandplan_initJoinLoop() LMICau921_initJoinLoop() void LMICau921_setBcnRxParams(void); #define LMICbandplan_setBcnRxParams() LMICau921_setBcnRxParams() @@ -60,4 +66,4 @@ void LMICau921_setRx1Params(void); void LMICau921_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(txbeg) LMICau921_updateTx(txbeg) -#endif // _lmic_au921_h_ +#endif // _lmic_bandplan_au921_h_ diff --git a/src/lmic/lmic_bandplan_eu868.h b/src/lmic/lmic_bandplan_eu868.h index d1e3adc..efff7d5 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,8 +33,10 @@ # include "lmic_eu_like.h" #endif +// return maximum frame length (including PHY header) for this data rate (eu868); 0 --> not valid dr. uint8_t LMICeu868_maxFrameLen(uint8_t dr); -#define maxFrameLen(dr) LMICeu868_maxFrameLen(dr) +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICeu868_maxFrameLen(dr) int8_t LMICeu868_pow2dBm(uint8_t mcmd_ladr_p1); #define pow2dBm(mcmd_ladr_p1) LMICeu868_pow2dBm(mcmd_ladr_p1) @@ -57,13 +59,7 @@ LMICeu868_isValidBeacon1(const uint8_t *d) { // override default for LMICbandplan_isFSK() #undef LMICbandplan_isFSK -#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == EU868_DR_FSK) - -// txDone handling for FSK. -void -LMICeu868_txDoneFSK(ostime_t delay, osjobcb_t func); - -#define LMICbandplan_txDoneFsk(delay, func) LMICeu868_txDoneFSK(delay, func) +#define LMICbandplan_isFSK() (/* RX datarate */LMIC.dndr == EU868_DR_FSK) #define LMICbandplan_getInitialDrJoin() (EU868_DR_SF7) @@ -89,4 +85,7 @@ void LMICeu868_initDefaultChannels(bit_t join); ostime_t LMICeu868_nextJoinTime(ostime_t now); #define LMICbandplan_nextJoinTime(now) LMICeu868_nextJoinTime(now) +void LMICeu868_setRx1Params(void); +#define LMICbandplan_setRx1Params() LMICeu868_setRx1Params() + #endif // _lmic_eu868_h_ diff --git a/src/lmic/lmic_bandplan_in866.h b/src/lmic/lmic_bandplan_in866.h index ad1b4e4..dad10ca 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,15 +26,17 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _lmic_in866_h_ -# define _lmic_in866_h_ +#ifndef _lmic_bandplan_in866_h_ +# define _lmic_bandplan_in866_h_ #ifndef _lmic_eu_like_h_ # include "lmic_eu_like.h" #endif +// return maximum frame length (including PHY header) for this data rate (in866); 0 --> not valid dr. uint8_t LMICin866_maxFrameLen(uint8_t dr); -#define maxFrameLen(dr) LMICin866_maxFrameLen(dr) +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICin866_maxFrameLen(dr) int8_t LMICin866_pow2dBm(uint8_t mcmd_ladr_p1); #define pow2dBm(mcmd_ladr_p1) LMICin866_pow2dBm(mcmd_ladr_p1) @@ -54,13 +56,7 @@ LMICin866_isValidBeacon1(const uint8_t *d) { // override default for LMICbandplan_isFSK() #undef LMICbandplan_isFSK -#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == IN866_DR_FSK) - -// txDone handling for FSK. -void -LMICin866_txDoneFSK(ostime_t delay, osjobcb_t func); - -#define LMICbandplan_txDoneFsk(delay, func) LMICin866_txDoneFSK(delay, func) +#define LMICbandplan_isFSK() (/* TX datarate */LMIC.dndr == IN866_DR_FSK) #define LMICbandplan_getInitialDrJoin() (IN866_DR_SF7) @@ -82,4 +78,7 @@ ostime_t LMICin866_nextJoinState(void); void LMICin866_initDefaultChannels(bit_t join); #define LMICbandplan_initDefaultChannels(join) LMICin866_initDefaultChannels(join) -#endif // _lmic_in866_h_ +void LMICin866_setRx1Params(void); +#define LMICbandplan_setRx1Params() LMICin866_setRx1Params() + +#endif // _lmic_bandplan_in866_h_ diff --git a/src/lmic/lmic_bandplan_kr920.h b/src/lmic/lmic_bandplan_kr920.h new file mode 100755 index 0000000..2c22f22 --- /dev/null +++ b/src/lmic/lmic_bandplan_kr920.h @@ -0,0 +1,91 @@ +/* +* Copyright (c) 2014-2016 IBM Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _lmic_kr920_h_ +# define _lmic_kr920_h_ + +#ifndef _lmic_eu_like_h_ +# include "lmic_eu_like.h" +#endif + +// return maximum frame length (including PHY header) for this data rate (kr920); 0 --> not valid dr. +uint8_t LMICkr920_maxFrameLen(uint8_t dr); +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICkr920_maxFrameLen(dr) + +int8_t LMICkr920_pow2dBm(uint8_t mcmd_ladr_p1); +#define pow2dBm(mcmd_ladr_p1) LMICkr920_pow2dBm(mcmd_ladr_p1) + +// Times for half symbol per DR +// Per DR table to minimize rounding errors +ostime_t LMICkr920_dr2hsym(uint8_t dr); +#define dr2hsym(dr) LMICkr920_dr2hsym(dr) + + +// TODO(tmm@mcci.com) this looks bogus compared to current 1.02 regional +// spec. https://github.com/mcci-catena/arduino-lmic/issues/18 +static inline int +LMICkr920_isValidBeacon1(const uint8_t *d) { + return d[OFF_BCN_CRC1] != (u1_t)os_crc16(d, OFF_BCN_CRC1); +} + +#undef LMICbandplan_isValidBeacon1 +#define LMICbandplan_isValidBeacon1(pFrame) LMICkr920_isValidBeacon1(pFrame) + +// override default for LMICbandplan_isFSK() +#undef LMICbandplan_isFSK +#define LMICbandplan_isFSK() (/* always false */ 0) + +#define LMICbandplan_getInitialDrJoin() (KR920_DR_SF7) + +void LMICkr920_setBcnRxParams(void); +#define LMICbandplan_setBcnRxParams() LMICkr920_setBcnRxParams() + +u4_t LMICkr920_convFreq(xref2cu1_t ptr); +#define LMICbandplan_convFreq(ptr) LMICkr920_convFreq(ptr) + +void LMICkr920_initJoinLoop(void); +#define LMICbandplan_initJoinLoop() LMICkr920_initJoinLoop() + +ostime_t LMICkr920_nextTx(ostime_t now); +#define LMICbandplan_nextTx(now) LMICkr920_nextTx(now) + +ostime_t LMICkr920_nextJoinState(void); +#define LMICbandplan_nextJoinState() LMICkr920_nextJoinState() + +void LMICkr920_initDefaultChannels(bit_t join); +#define LMICbandplan_initDefaultChannels(join) LMICkr920_initDefaultChannels(join) + +void LMICkr920_setRx1Params(void); +#define LMICbandplan_setRx1Params() LMICkr920_setRx1Params() + +#undef LMICbandplan_updateTx +void LMICkr920_updateTx(ostime_t txbeg); +#define LMICbandplan_updateTx(t) LMICkr920_updateTx(t) + +#endif // _lmic_kr920_h_ diff --git a/src/lmic/lmic_bandplan_us915.h b/src/lmic/lmic_bandplan_us915.h index 28ae2c8..e08a795 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,26 +26,30 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _lmic_us915_h_ -# define _lmic_us915_h_ +#ifndef _lmic_bandplan_us915_h_ +# define _lmic_bandplan_us915_h_ // preconditions for lmic_us_like.h -#define LMICuslike_getFirst500kHzDR() (US915_DR_SF8C) +#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR4) +#define LMICuslike_getJoin125kHzDR() (LORAWAN_DR0) #ifndef _lmic_us_like_h_ # include "lmic_us_like.h" #endif +// return maximum frame length (including PHY header) for this data rate (us915); 0 --> not valid dr. uint8_t LMICus915_maxFrameLen(uint8_t dr); -#define maxFrameLen(dr) LMICus915_maxFrameLen(dr) +// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. +#define LMICbandplan_maxFrameLen(dr) LMICus915_maxFrameLen(dr) -#define pow2dBm(mcmd_ladr_p1) ((s1_t)(US915_TX_MAX_DBM - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1))) +int8_t LMICus915_pow2dbm(uint8_t mcmd_ladr_p1); +#define pow2dBm(mcmd_ladr_p1) LMICus915_pow2dbm(mcmd_ladr_p1) ostime_t LMICus915_dr2hsym(uint8_t dr); #define dr2hsym(dr) LMICus915_dr2hsym(dr) -#define LMICbandplan_getInitialDrJoin() (US915_DR_SF7) +#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR0) void LMICus915_setBcnRxParams(void); #define LMICbandplan_setBcnRxParams() LMICus915_setBcnRxParams() @@ -53,10 +57,13 @@ void LMICus915_setBcnRxParams(void); u4_t LMICus915_convFreq(xref2cu1_t ptr); #define LMICbandplan_convFreq(ptr) LMICus915_convFreq(ptr) +void LMICus915_initJoinLoop(void); +#define LMICbandplan_initJoinLoop() LMICus915_initJoinLoop() + void LMICus915_setRx1Params(void); #define LMICbandplan_setRx1Params() LMICus915_setRx1Params() void LMICus915_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(txbeg) -#endif // _lmic_us915_h_ +#endif // _lmic_bandplan_us915_h_ diff --git a/src/lmic/lmic_compliance.c b/src/lmic/lmic_compliance.c new file mode 100755 index 0000000..a70a8e4 --- /dev/null +++ b/src/lmic/lmic_compliance.c @@ -0,0 +1,768 @@ +/* + +Module: lmic_compliance.c + +Function: + Implementation of the compliance engine. + +Copyright notice and license info: + See LICENSE file accompanying this project. + +Author: + Terry Moore, MCCI Corporation March 2019 + +Description: + See function descriptions. + +*/ + +#include "lmic.h" +#include "lmic_compliance.h" +#include "lorawan_spec_compliance.h" +#include +#include + +#if defined(LMIC_PRINTF_TO) +# include +# define LMIC_COMPLIANCE_PRINTF(f, ...) printf(f, ## __VA_ARGS__) +#else +# define LMIC_COMPLIANCE_PRINTF(f, ...) do { ; } while (0) +#endif + +/****************************************************************************\ +| +| Manifest constants and local declarations. +| +\****************************************************************************/ + +static void acEnterActiveMode(void); +static void acExitActiveMode(void); +static void acSendUplink(void); +static void acSetTimer(ostime_t); +static void acSendUplinkBuffer(void); +static void evActivate(void); +static void evDeactivate(void); +static void evJoinCommand(void); +static void evMessage(const uint8_t *pMessage, size_t nMessage); +static lmic_compliance_fsmstate_t fsmDispatch(lmic_compliance_fsmstate_t, bool); +static void fsmEval(void); +static void fsmEvalDeferred(void); +static osjobcbfn_t fsmJobCb; +static bool isActivateMessage(const uint8_t *pMessage, size_t nMessage); +static void evEchoCommand(const uint8_t *pMessage, size_t nMessage); +static lmic_event_cb_t lmicEventCb; +static lmic_txmessage_cb_t sendUplinkCompleteCb; +static osjobcbfn_t timerExpiredCb; + +/* these are declared global so the optimizer can chuck them without warnings */ +const char *LMICcompliance_txSuccessToString(int fSuccess); +const char * LMICcompliance_fsmstate_getName(lmic_compliance_fsmstate_t state); + +/****************************************************************************\ +| +| Read-only data. +| +\****************************************************************************/ + +/****************************************************************************\ +| +| Variables. +| +\****************************************************************************/ + +lmic_compliance_t LMIC_Compliance; + +/* + +Name: LMIC_complianceRxMessage() + +Function: + Add compliance-awareness to LMIC applications by filtering messages. + +Definition: + lmic_compliance_rx_action_t LMIC_complianceRxMessage( + u1_t port, + const u1_t *pMessage, + size_t nMessage + ); + +Description: + Clients who want to handle the LoRaWAN compliance protocol on + port 224 should call this routine each time a downlink message is + received. This function will update the internal compliance state, + and return an appropriate action to the user. + + If the result is `LMIC_COMPLIANCE_RX_ACTION_PROCESS`, then the client should + process the message as usual. Otherwise, the client should discard the + message. The other values further allow the client to track entry into, + and exit from, compliance state. `LMIC_COMPLIANCE_RX_ACTION_START` signals + entry into compliance state; `LMIC_COMPLIANCE_RX_ACTION_END` signals exit + from compliance state; and `LMIC_COMPLIANCE_RX_ACTION_IGNORE` indicates + a mesage that should be discarded while already in compliance + state. + +Returns: + See description. + +*/ + +lmic_compliance_rx_action_t +LMIC_complianceRxMessage( + uint8_t port, + const uint8_t *pMessage, + size_t nMessage +) { + lmic_compliance_state_t const complianceState = LMIC_Compliance.state; + + // update the counter used by the status message. + ++LMIC_Compliance.downlinkCount; + + // filter normal messages. + if (port != LORAWAN_PORT_COMPLIANCE) { + return lmic_compliance_state_IsActive(complianceState) + ? LMIC_COMPLIANCE_RX_ACTION_PROCESS + : LMIC_COMPLIANCE_RX_ACTION_IGNORE + ; + } + + // it's a message to port 224. + // if we're not active, ignore everything but activation messages + if (! lmic_compliance_state_IsActive(complianceState)) { + if (isActivateMessage(pMessage, nMessage)) { + evActivate(); + } // else ignore. + } else { + evMessage(pMessage, nMessage); + } + if (lmic_compliance_state_IsActive(complianceState) == lmic_compliance_state_IsActive(LMIC_Compliance.state)) + return LMIC_COMPLIANCE_RX_ACTION_IGNORE; + else if (! lmic_compliance_state_IsActive(complianceState)) + return LMIC_COMPLIANCE_RX_ACTION_START; + else + return LMIC_COMPLIANCE_RX_ACTION_END; +} + +/* + +Name: isActivateMessage() + +Function: + See whether a message is a LoRaWAN activate test mode message. + +Definition: + static bool isActivateMessage( + const uint8_t *pMessage, + size_t nMessage + ); + +Description: + The message body is compared to an activate message (per the + LoRa Alliance End Device Certification spec). + +Returns: + The result is `true` if the message is an activation message; + it's `false` otherwise. + +*/ + +static bool +isActivateMessage( + const uint8_t *pMessage, + size_t nMessage +) { + const uint8_t body[LORAWAN_COMPLIANCE_CMD_ACTIVATE_LEN] = { + LORAWAN_COMPLIANCE_CMD_ACTIVATE, + LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC, + LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC, + LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC, + }; + + if (nMessage != sizeof(body)) + return false; + + if (memcmp(pMessage, body, sizeof(body)) == 0) + return true; + else + return false; +} + +/* + +Name: evActivate() + +Function: + Report an activation event to the finite state machine. + +Definition: + void evActivate(void); + +Description: + We report an activation event, and re-evaluate the FSM. + +Returns: + No explicit result. + +*/ + +static void evActivate(void) { + if (! lmic_compliance_state_IsActive(LMIC_Compliance.state)) { + LMIC_Compliance.downlinkCount = 0; + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_ACTIVATE; + LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_ACTIVATING; + + LMIC_Compliance.saveEvent.pEventCb = LMIC.client.eventCb; + LMIC_Compliance.saveEvent.pUserData = LMIC.client.eventUserData; + +#if CFG_LMIC_EU_like + band_t *b = LMIC.bands; + lmic_compliance_band_t *b_save = LMIC_Compliance.saveBands; + + for (; b < &LMIC.bands[MAX_BANDS]; ++b, ++b_save) { + b_save->txcap = b->txcap; + b->txcap = 1; + b->avail = os_getTime(); + } +#endif // CFG_LMIC_EU_like + + LMIC_registerEventCb(lmicEventCb, NULL); + + fsmEvalDeferred(); + } else { + LMIC_COMPLIANCE_PRINTF("Redundant ActivateTM message ignored.\n"); + } +} + +/* + +Name: evMessage() + +Function: + Process an inbound message while active. + +Definition: + void evMessage(const uint8_t *pMessage, size_t nMessage); + +Description: + The event is parsed, and the appropriate event(s) are sent into + the finite state machine. Note that because of the way the LMIC + works, we can assume that no uplink event is pending; so it's safe + to launch a send from here. + +Returns: + No explicit result. + +*/ + +static void evMessage( + const uint8_t *pMessage, + size_t nMessage +) { + if (nMessage == 0) + return; + + const uint8_t cmd = pMessage[0]; + switch (cmd) { + case LORAWAN_COMPLIANCE_CMD_DEACTIVATE: { + evDeactivate(); + break; + } + case LORAWAN_COMPLIANCE_CMD_ACTIVATE: { + if (isActivateMessage(pMessage, nMessage)) + evActivate(); + break; + } + case LORAWAN_COMPLIANCE_CMD_SET_CONFIRM: { + LMIC_Compliance.fsmFlags |= LMIC_COMPLIANCE_FSM_CONFIRM; + break; + } + case LORAWAN_COMPLIANCE_CMD_SET_UNCONFIRM: { + LMIC_Compliance.fsmFlags &= ~LMIC_COMPLIANCE_FSM_CONFIRM; + break; + } + case LORAWAN_COMPLIANCE_CMD_ECHO: { + evEchoCommand(pMessage, nMessage); + break; + } + case LORAWAN_COMPLIANCE_CMD_LINK: { + // not clear what this request does. + break; + } + case LORAWAN_COMPLIANCE_CMD_JOIN: { + evJoinCommand(); + break; + } + default: + break; + } +} + +/* + +Name: evDeactivate() + +Function: + Report an deactivation event to the finite state machine. + +Definition: + void evDectivate(void); + +Description: + We report a deactivation event, and re-evaluate the FSM. + We also set a flag so that we're return the appropriate + status from the compliance entry point to the real + application. + +Returns: + No explicit result. + +*/ + +static void evDeactivate(void) { + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_DEACTIVATE; + LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_STOPPING; + + // restore user's event handler. + LMIC_registerEventCb(LMIC_Compliance.saveEvent.pEventCb, LMIC_Compliance.saveEvent.pUserData); + + // restore band settings +#if CFG_LMIC_EU_like + band_t *b = LMIC.bands; + lmic_compliance_band_t const *b_save = LMIC_Compliance.saveBands; + + for (; b < &LMIC.bands[MAX_BANDS]; ++b, ++b_save) { + b->txcap = b_save->txcap; + } +#endif // CFG_LMIC_EU_like + + fsmEvalDeferred(); +} + +/* + +Name: evJoinCommand() + +Function: + Report that a join has been commanded. + +Definition: + void evJoinCommand(void); + +Description: + We unjoin from the network, and then report a deactivation + of test mode. That will get us out of test mode and back + to the compliance app. The next message send will trigger + a join. + +Returns: + No explicit result. + +*/ + +static void evJoinCommand( + void +) { + LMIC_unjoin(); + evDeactivate(); +} + +/* + +Name: evEchoCommand() + +Function: + Format and transmit the response to an echo downlink (aka echo request). + +Definition: + void evEchoCommand( + const uint8_t *pMessage, + size_t nMessage + ); + +Description: + The echo response is formatted and transmitted. Since we just received + a downlink, it's always safe to do this. + +Returns: + No explicit result. + +*/ + +static void evEchoCommand( + const uint8_t *pMessage, + size_t nMessage +) { + uint8_t *pResponse; + + if (nMessage > sizeof(LMIC_Compliance.uplinkMessage)) + return; + + // create the echo message. + pResponse = LMIC_Compliance.uplinkMessage; + + // copy the command byte unchanged. + *pResponse++ = *pMessage++; + --nMessage; + + // each byte in the body has to be incremented by one. + for (; nMessage > 0; --nMessage) { + *pResponse++ = (uint8_t)(*pMessage++ + 1); + } + + // now that the message is formatted, tell the fsm to send it; + // need to use a separate job. + LMIC_Compliance.uplinkSize = (uint8_t) (pResponse - LMIC_Compliance.uplinkMessage); + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_ECHO_REQUEST; + fsmEvalDeferred(); +} + + +/* + +Name: fsmEval() + +Function: + Evaluate the FSM, preventing recursion. + +Definition: + void fsmEval(void); + +Description: + We check for a nested call to evaluate the FSM; + if detected, the processing is deferred until the + current evaluation completes. Otherwise, we start + a new FSM evaluation, which proceeds until the FSM + returns a "no-change" result. + +Returns: + No explicit result. + +*/ + +const char * LMICcompliance_fsmstate_getName(lmic_compliance_fsmstate_t state) { + const char * const names[] = { LMIC_COMPLIANCE_FSMSTATE__NAMES }; + + if ((unsigned) state >= sizeof(names)/sizeof(names[0])) + return "<>"; + else + return names[state]; +} + +static void fsmEvalDeferred(void) { + os_setCallback(&LMIC_Compliance.fsmJob, fsmJobCb); +} + +static void fsmJobCb(osjob_t *j) { + LMIC_API_PARAMETER(j); + fsmEval(); +} + +static void fsmEval(void) { + bool fNewState; + + // check for reentry. + do { + lmic_compliance_fsmflags_t const fsmFlags = LMIC_Compliance.fsmFlags; + + if (fsmFlags & LMIC_COMPLIANCE_FSM_ACTIVE) { + LMIC_Compliance.fsmFlags = fsmFlags | LMIC_COMPLIANCE_FSM_REENTERED; + return; + } + + // record that we're active + LMIC_Compliance.fsmFlags = fsmFlags | LMIC_COMPLIANCE_FSM_ACTIVE; + } while (0); + + // evaluate and change state + fNewState = false; + for (;;) { + lmic_compliance_fsmstate_t const oldState = LMIC_Compliance.fsmState; + lmic_compliance_fsmstate_t newState; + + newState = fsmDispatch(oldState, fNewState); + + if (newState == LMIC_COMPLIANCE_FSMSTATE_NOCHANGE) { + lmic_compliance_fsmflags_t const fsmFlags = LMIC_Compliance.fsmFlags; + + if ((fsmFlags & LMIC_COMPLIANCE_FSM_REENTERED) == 0) { + // not reentered, no change: get out. + LMIC_Compliance.fsmFlags = fsmFlags & ~LMIC_COMPLIANCE_FSM_ACTIVE; + return; + } else { + // reentered. reset reentered flag and keep going. + LMIC_Compliance.fsmFlags = fsmFlags & ~LMIC_COMPLIANCE_FSM_REENTERED; + fNewState = false; + } + } else { + // state change! + LMIC_COMPLIANCE_PRINTF("%s: change state %s(%u) => %s(%u)\n", + __func__, + LMICcompliance_fsmstate_getName(oldState), (unsigned) oldState, + LMICcompliance_fsmstate_getName(newState), (unsigned) newState + ); + fNewState = true; + LMIC_Compliance.fsmState = newState; + } + } +} + +/* + +Name: fsmDispatch() + +Function: + Dispatch to the appropriate event handler. + +Definition: + lmic_compliance_fsmstate_t fsmDispatch( + lmic_compliance_fsmstate_t state, + bool fEntry + ); + +Description: + This function is called by the evalutator as needed. `state` + is set to the current state of the FSM, and `fEntry` is + true if and only if this state has just been entered via a + transition arrow. (Might be a transition to self.) + +Returns: + This function returns LMIC_COMPLIANCE_FSMSTATE_NOCHANGE if + the FSM is to remain in this state until an event occurs. + Otherwise it returns the new state. + +*/ + +static inline lmic_compliance_eventflags_t +eventflags_TestAndClear(lmic_compliance_eventflags_t flag) { + const lmic_compliance_eventflags_t old = LMIC_Compliance.eventflags; + const lmic_compliance_eventflags_t result = old & flag; + + if (result != 0) + LMIC_Compliance.eventflags = old ^ result; + + return result; +} + +static lmic_compliance_fsmstate_t +fsmDispatch( + lmic_compliance_fsmstate_t state, + bool fEntry +) { + lmic_compliance_fsmstate_t newState; + + // currently, this is a stub. + newState = LMIC_COMPLIANCE_FSMSTATE_NOCHANGE; + + switch (state) { + case LMIC_COMPLIANCE_FSMSTATE_INITIAL: { + newState = LMIC_COMPLIANCE_FSMSTATE_INACTIVE; + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_INACTIVE: { + if (fEntry) { + acExitActiveMode(); + } + + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_ACTIVATE)) { + newState = LMIC_COMPLIANCE_FSMSTATE_ACTIVE; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_ACTIVE: { + if (fEntry) { + acEnterActiveMode(); + acSetTimer(sec2osticks(1)); + } + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) { + newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_TXBUSY: { + if (fEntry) { + acSetTimer(sec2osticks(1)); + } + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) { + newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_TESTMODE: { + if (LMIC.opmode & OP_TXDATA) { + // go back and wait some more. + newState = LMIC_COMPLIANCE_FSMSTATE_TXBUSY; + } + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_DEACTIVATE)) { + newState = LMIC_COMPLIANCE_FSMSTATE_INACTIVE; + } else if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_ECHO_REQUEST)) { + newState = LMIC_COMPLIANCE_FSMSTATE_ECHOING; + } else { + newState = LMIC_COMPLIANCE_FSMSTATE_REPORTING; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_ECHOING: { + if (fEntry) + acSendUplinkBuffer(); + + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE)) { + newState = LMIC_COMPLIANCE_FSMSTATE_RECOVERY; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_REPORTING: { + if (fEntry) + acSendUplink(); + + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE)) { + newState = LMIC_COMPLIANCE_FSMSTATE_RECOVERY; + } + break; + } + + case LMIC_COMPLIANCE_FSMSTATE_RECOVERY: { + if (fEntry) { + if (LMIC_Compliance.eventflags & (LMIC_COMPLIANCE_EVENT_DEACTIVATE | + LMIC_COMPLIANCE_EVENT_ECHO_REQUEST)) { + acSetTimer(sec2osticks(1)); + } else { + acSetTimer(sec2osticks(5)); + } + } + + if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) { + newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE; + } + break; + } + + default: { + break; + } + } + + return newState; +} + +static void acEnterActiveMode(void) { + // indicate to the outer world that we're active. + LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_ACTIVE; +} + +void acSetTimer(ostime_t delay) { + os_setTimedCallback(&LMIC_Compliance.timerJob, os_getTime() + delay, timerExpiredCb); +} + +static void timerExpiredCb(osjob_t *j) { + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED; + fsmEval(); +} + +static void lmicEventCb( + void *pUserData, + ev_t ev +) { + LMIC_API_PARAMETER(pUserData); + + // pass to user handler + if (LMIC_Compliance.saveEvent.pEventCb) { + LMIC_Compliance.saveEvent.pEventCb( + LMIC_Compliance.saveEvent.pUserData, ev + ); + } + + // if it's a EV_JOINED, or a TXCMOMPLETE, we should tell the FSM. + if ((UINT32_C(1) << ev) & (EV_JOINED | EV_TXCOMPLETE)) { + fsmEvalDeferred(); + } +} + + +static void acExitActiveMode(void) { + LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_IDLE; + os_clearCallback(&LMIC_Compliance.timerJob); + LMIC_clrTxData(); +} + + +static void acSendUplink(void) { + uint8_t payload[2]; + uint32_t const downlink = LMIC_Compliance.downlinkCount; + + // build the uplink message + payload[0] = (uint8_t) (downlink >> 8); + payload[1] = (uint8_t) downlink; + + // reset the flags + LMIC_Compliance.eventflags &= ~LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; + + // don't try to send if busy; might be sending echo message. + lmic_tx_error_t const eSend = + LMIC_sendWithCallback_strict( + LORAWAN_PORT_COMPLIANCE, + payload, sizeof(payload), + /* confirmed? */ + !! (LMIC_Compliance.fsmFlags & LMIC_COMPLIANCE_FSM_CONFIRM), + sendUplinkCompleteCb, NULL + ); + + if (eSend == LMIC_ERROR_SUCCESS) { + // queued successfully + LMIC_COMPLIANCE_PRINTF( + "lmic_compliance.%s: queued uplink message(%u, %p)\n", + __func__, + (unsigned) downlink & 0xFFFF, + LMIC.client.txMessageCb + ); + } else { + // failed to queue; just skip this cycle. + LMIC_COMPLIANCE_PRINTF( + "lmic_compliance.%s: error(%d) sending uplink message(%u), %u bytes\n", + __func__, + eSend, + (unsigned) downlink & 0xFFFF, + LMIC.client.txMessageCb + ); + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; + fsmEval(); + } +} + +static void sendUplinkCompleteCb(void *pUserData, int fSuccess) { + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; + LMIC_COMPLIANCE_PRINTF("%s(%s)\n", __func__, LMICcompliance_txSuccessToString(fSuccess)); + fsmEvalDeferred(); +} + +static void acSendUplinkBuffer(void) { + // send uplink data. + lmic_tx_error_t const eSend = + LMIC_sendWithCallback_strict( + LORAWAN_PORT_COMPLIANCE, + LMIC_Compliance.uplinkMessage, LMIC_Compliance.uplinkSize, + /* confirmed? */ (LMIC_Compliance.fsmFlags & LMIC_COMPLIANCE_FSM_CONFIRM) != 0, + sendUplinkCompleteCb, + NULL); + + if (eSend == LMIC_ERROR_SUCCESS) { + LMIC_COMPLIANCE_PRINTF("%s: queued %u bytes\n", __func__, LMIC_Compliance.uplinkSize); + } else { + LMIC_COMPLIANCE_PRINTF("%s: uplink %u bytes failed (error %d)\n", __func__, LMIC_Compliance.uplinkSize, eSend); + if (eSend == LMIC_ERROR_TX_NOT_FEASIBLE) { + // Reverse the increment of the downlink count. Needed for US compliance. + if (CFG_region == LMIC_REGION_us915) + --LMIC_Compliance.downlinkCount; + } + LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; + fsmEval(); + } +} + +const char *LMICcompliance_txSuccessToString(int fSuccess) { + return fSuccess ? "ok" : "failed"; +} diff --git a/src/lmic/lmic_compliance.h b/src/lmic/lmic_compliance.h new file mode 100755 index 0000000..8efe0e8 --- /dev/null +++ b/src/lmic/lmic_compliance.h @@ -0,0 +1,138 @@ +/* + +Module: lmic_compliance.h + +Function: + Internal header file for compliance-related work. + +Copyright notice and license info: + See LICENSE file accompanying this project. + +Author: + Terry Moore, MCCI Corporation March 2019 + +Description: + This header file allows us to break up the compliance + functions into multiple .c files if we wish. + +*/ + +#ifndef _lmic_compliance_h_ /* prevent multiple includes */ +#define _lmic_compliance_h_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#ifndef _lmic_h_ +# include "lmic.h" +#endif + +#include +#include + +typedef struct lmic_compliance_s lmic_compliance_t; + +// concrete type for the state enumeration for the compliance engine. +typedef uint8_t lmic_compliance_state_t; + +enum lmic_compliance_state_e { + LMIC_COMPLIANCE_STATE_IDLE = 0, // app state + LMIC_COMPLIANCE_STATE_STOPPING = 1, // transitioning back to app + LMIC_COMPLIANCE_STATE_ACTIVATING = 2, // transitioning to compliance state + LMIC_COMPLIANCE_STATE_ACTIVE = 3, // in compliance state +}; + +// return true if a state value indicates that the FSM is active. +static inline bool +lmic_compliance_state_IsActive(lmic_compliance_state_t s) { + return s >= LMIC_COMPLIANCE_STATE_ACTIVATING; +} + +// events from the outside world to the FSM +typedef uint8_t lmic_compliance_eventflags_t; + +enum lmic_compliance_eventflags_e { + LMIC_COMPLIANCE_EVENT_ACTIVATE = 1u << 0, + LMIC_COMPLIANCE_EVENT_DEACTIVATE = 1u << 1, + LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED = 1u << 2, + LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE = 1u << 3, + LMIC_COMPLIANCE_EVENT_ECHO_REQUEST = 1u << 4, +}; + +typedef uint8_t lmic_compliance_fsmflags_t; +enum lmic_compliance_fsmflags_e { + LMIC_COMPLIANCE_FSM_ACTIVE = 1u << 0, + LMIC_COMPLIANCE_FSM_REENTERED = 1u << 1, + LMIC_COMPLIANCE_FSM_CONFIRM = 1u << 2, +}; + +typedef uint8_t lmic_compliance_fsmstate_t; +enum lmic_compliance_fsmstate_e { + LMIC_COMPLIANCE_FSMSTATE_INITIAL = 0, + LMIC_COMPLIANCE_FSMSTATE_NOCHANGE = 1, + LMIC_COMPLIANCE_FSMSTATE_ACTIVE = 2, + LMIC_COMPLIANCE_FSMSTATE_INACTIVE = 3, + LMIC_COMPLIANCE_FSMSTATE_TESTMODE = 4, // sending test uplinks + LMIC_COMPLIANCE_FSMSTATE_ECHOING = 5, + LMIC_COMPLIANCE_FSMSTATE_REPORTING = 6, + LMIC_COMPLIANCE_FSMSTATE_RECOVERY = 7, + LMIC_COMPLIANCE_FSMSTATE_TXBUSY = 8, +}; + +#define LMIC_COMPLIANCE_FSMSTATE__NAMES \ + "INITIAL", "NOCHANGE", "ACTIVE", "INACTIVE", "TESTMODE", \ + "ECHOING", "REPORTING", "RECOVERY", "TXBUSY" + +typedef struct lmic_compliance_eventcb_s lmic_compliance_eventcb_t; +struct lmic_compliance_eventcb_s { + // save the user's event CB while active. + lmic_event_cb_t *pEventCb; + // save the user's event data while active. + void *pUserData; +}; + +// structure for saving band settings during test +typedef struct lmic_compliance_band_s lmic_compliance_band_t; +struct lmic_compliance_band_s { + u2_t txcap; // saved 1/duty cycle +}; + +// the state of the compliance engine. +struct lmic_compliance_s { + // uint64 + // uintptr + osjob_t timerJob; // the job for driving uplinks + osjob_t fsmJob; // job for reevaluating the FSM. + lmic_compliance_eventcb_t saveEvent; // the user's event handler. + + // uint32 + + // uint16 +#if CFG_LMIC_EU_like + lmic_compliance_band_t saveBands[MAX_BANDS]; +#endif // CFG_LMIC_EU_like + + // we are required to maintain a downlink count + // that is reset on join/test entry and incremented for + // each valid test message. + uint16_t downlinkCount; + + // uint8 + + lmic_compliance_state_t state; // current state of compliance engine. + lmic_compliance_eventflags_t eventflags; // incoming events. + lmic_compliance_fsmflags_t fsmFlags; // FSM operational flags + lmic_compliance_fsmstate_t fsmState; // FSM current state + + uint8_t uplinkSize; + uint8_t uplinkMessage[MAX_LEN_PAYLOAD]; +}; + +extern lmic_compliance_t LMIC_Compliance; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* _lmic_compliance_h_ */ \ No newline at end of file diff --git a/src/lmic/lmic_config_preconditions.h b/src/lmic/lmic_config_preconditions.h index 49be1d5..4cfca25 100755 --- a/src/lmic/lmic_config_preconditions.h +++ b/src/lmic/lmic_config_preconditions.h @@ -70,7 +70,7 @@ Revision history: #define LMIC_REGION_au921 5 #define LMIC_REGION_cn490 6 #define LMIC_REGION_as923 7 -#define LMIC_REGION_kr921 8 +#define LMIC_REGION_kr920 8 #define LMIC_REGION_in866 9 // Some regions have country-specific overrides. For generality, we specify @@ -104,7 +104,7 @@ Revision history: (1 << LMIC_REGION_au921) | \ /* (1 << LMIC_REGION_cn490) | */ \ (1 << LMIC_REGION_as923) | \ - /* (1 << LMIC_REGION_kr921) | */ \ + (1 << LMIC_REGION_kr920) | \ (1 << LMIC_REGION_in866) | \ 0) @@ -128,7 +128,7 @@ Revision history: (defined(CFG_au921) << LMIC_REGION_au921) | \ (defined(CFG_cn490) << LMIC_REGION_cn490) | \ (defined(CFG_as923) << LMIC_REGION_as923) | \ - (defined(CFG_kr921) << LMIC_REGION_kr921) | \ + (defined(CFG_kr920) << LMIC_REGION_kr920) | \ (defined(CFG_in866) << LMIC_REGION_in866) | \ 0) @@ -153,8 +153,8 @@ Revision history: # define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP #elif defined(CFG_as923) # define CFG_region LMIC_REGION_as923 -#elif defined(CFG_kr921) -# define CFG_region LMIC_REGION_kr921 +#elif defined(CFG_kr920) +# define CFG_region LMIC_REGION_kr920 #elif defined(CFG_in866) # define CFG_region LMIC_REGION_in866 #else @@ -174,7 +174,7 @@ Revision history: /* (1 << LMIC_REGION_au921) | */ \ /* (1 << LMIC_REGION_cn490) | */ \ (1 << LMIC_REGION_as923) | \ - (1 << LMIC_REGION_kr921) | \ + (1 << LMIC_REGION_kr920) | \ (1 << LMIC_REGION_in866) | \ 0) @@ -192,7 +192,7 @@ Revision history: (1 << LMIC_REGION_au921) | \ /* (1 << LMIC_REGION_cn490) | */ \ /* (1 << LMIC_REGION_as923) | */ \ - /* (1 << LMIC_REGION_kr921) | */ \ + /* (1 << LMIC_REGION_kr920) | */ \ /* (1 << LMIC_REGION_in866) | */ \ 0) @@ -204,4 +204,13 @@ Revision history: #define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK)) #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, +// 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 + #endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */ diff --git a/src/lmic/lmic_env.h b/src/lmic/lmic_env.h index 4610fb3..06793e0 100755 --- a/src/lmic/lmic_env.h +++ b/src/lmic/lmic_env.h @@ -7,7 +7,7 @@ Function: Copyright notice and license info: See LICENSE file accompanying this project. - + Author: Terry Moore, MCCI Corporation November 2018 @@ -39,7 +39,7 @@ Description: error. The results of using this macro where a declaration is not permitted are unspecified. - This is different from #if !(fErrorIfFalse) / #error in that the + This is different from #if !(fErrorIfFalse) / #error in that the expression is evaluated by the compiler rather than by the pre- processor. Therefore things like sizeof() can be used. @@ -214,4 +214,38 @@ Returns: # endif #endif +/* + +Macro: LMIC_DECLARE_FUNCTION_WEAK() + +Function: + Declare an external function as a weak reference. + +Definition: + #define LMIC_DECLARE_FUNCTION_WEAK(ReturnType, FunctionName, Params) ... + +Description: + This macro generates a weak reference to the specified function. + +Example: + LMIC_DECLARE_FUNCTION_WEAK(void, onEvent, (ev_t e)); + + This saya that onEvent is a weak external reference. When calling + onEvent, you must always first check whether it's supplied: + + if (onEvent != NULL) + onEvent(e); + +Returns: + This macro expands to a declaration, without a trailing semicolon. + +Notes: + This form allows for compilers that use _Pragma(weak, name) instead + of inline attributes. + +*/ + +#define LMIC_DECLARE_FUNCTION_WEAK(a_ReturnType, a_FunctionName, a_Params) \ + a_ReturnType __attribute__((__weak__)) a_FunctionName a_Params + #endif /* _lmic_env_h_ */ \ No newline at end of file diff --git a/src/lmic/lmic_eu868.c b/src/lmic/lmic_eu868.c index 3c87334..f8d51d1 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,21 +49,28 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; -static CONST_TABLE(u1_t, maxFrameLens)[] = { 64,64,64,123 }; +static CONST_TABLE(u1_t, maxFrameLens)[] = { + 59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5 +}; uint8_t LMICeu868_maxFrameLen(uint8_t dr) { if (dr < LENOF_TABLE(maxFrameLens)) return TABLE_GET_U1(maxFrameLens, dr); else - return 0xFF; + return 0; } static CONST_TABLE(s1_t, TXPOWLEVELS)[] = { - 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0 + 16, 14, 12, 10, 8, 6, 4, 2 }; int8_t LMICeu868_pow2dBm(uint8_t mcmd_ladr_p1) { - return TABLE_GET_S1(TXPOWLEVELS, (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT); + uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT; + if (pindex < LENOF_TABLE(TXPOWLEVELS)) { + return TABLE_GET_S1(TXPOWLEVELS, pindex); + } else { + return -128; + } } // only used in this module, but used by variant macro dr2hsym(). @@ -75,7 +82,7 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { us2osticksRound(128 << 3), // DR_SF8 us2osticksRound(128 << 2), // DR_SF7 us2osticksRound(128 << 1), // DR_SF7B - us2osticksRound(80) // FSK -- not used (time for 1/2 byte) + us2osticksRound(80) // FSK -- time for 1/2 byte (unused by LMIC) }; ostime_t LMICeu868_dr2hsym(uint8_t dr) { @@ -93,6 +100,9 @@ static CONST_TABLE(u4_t, iniChannelFreq)[6] = { void LMICeu868_initDefaultChannels(bit_t join) { os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); +#if !defined(DISABLE_MCMD_DlChannelReq) + os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq)); +#endif // !DISABLE_MCMD_DlChannelReq os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); @@ -104,18 +114,9 @@ void LMICeu868_initDefaultChannels(bit_t join) { LMIC.channelDrMap[fu] = DR_RANGE_MAP(EU868_DR_SF12, EU868_DR_SF7); } - LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1% - LMIC.bands[BAND_MILLI].txpow = 14; - LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS; - LMIC.bands[BAND_CENTI].txcap = 100; // 1% - LMIC.bands[BAND_CENTI].txpow = 14; - LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS; - LMIC.bands[BAND_DECI].txcap = 10; // 10% - LMIC.bands[BAND_DECI].txpow = 27; - LMIC.bands[BAND_DECI].lastchnl = os_getRndU1() % MAX_CHANNELS; - LMIC.bands[BAND_MILLI].avail = - LMIC.bands[BAND_CENTI].avail = - LMIC.bands[BAND_DECI].avail = os_getTime(); + (void) LMIC_setupBand(BAND_MILLI, 14 /* dBm */, 1000 /* 0.1% */); + (void) LMIC_setupBand(BAND_CENTI, 14 /* dBm */, 100 /* 1% */); + (void) LMIC_setupBand(BAND_DECI, 27 /* dBm */, 10 /* 10% */); } bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { @@ -129,26 +130,59 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { return 1; } +// this table is from highest to lowest +static CONST_TABLE(u4_t, bandAssignments)[] = { + 870000000 /* .. and above */ | BAND_MILLI, + 869700000 /* .. 869700000 */ | BAND_CENTI, + 869650000 /* .. 869700000 */ | BAND_MILLI, + 869400000 /* .. 869650000 */ | BAND_DECI, + 868600000 /* .. 869640000 */ | BAND_MILLI, + 865000000 /* .. 868400000 */ | BAND_CENTI, +}; + 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; + } + bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) return 0; + if (band == -1) { - if (freq >= 869400000 && freq <= 869650000) - freq |= BAND_DECI; // 10% 27dBm - else if ((freq >= 868000000 && freq <= 868600000) || - (freq >= 869700000 && freq <= 870000000)) - freq |= BAND_CENTI; // 1% 14dBm - else - freq |= BAND_MILLI; // 0.1% 14dBm - } - else { - if (band > BAND_AUX) return 0; - freq = (freq&~3) | band; + for (u1_t i = 0; i < LENOF_TABLE(bandAssignments); ++i) { + const u4_t thisFreqBand = TABLE_GET_U4(bandAssignments, i); + const u4_t thisFreq = thisFreqBand & ~3; + if (freq >= thisFreq) { + band = ((u1_t)thisFreqBand & 3); + break; + } + } + + // if we didn't identify a frequency, it's millis. + if (band == -1) { + band = BAND_MILLI; + } } + + if ((u1_t)band > BAND_AUX) + return 0; + + freq |= band; + LMIC.channelFreq[chidx] = freq; - // TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(EU868_DR_SF12, EU868_DR_SF7) : drmap; - LMIC.channelMap |= 1 << chidx; // enabled right away + if (fEnable) + LMIC.channelMap |= 1 << chidx; // enabled right away + else + LMIC.channelMap &= ~(1 << chidx); return 1; } @@ -213,12 +247,28 @@ ostime_t LMICeu868_nextJoinState(void) { } #endif // !DISABLE_JOIN -// txDone handling for FSK. -void -LMICeu868_txDoneFSK(ostime_t delay, osjobcb_t func) { - LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); - LMIC.rxsyms = RXLEN_FSK; - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +// set the Rx1 dndr, rps. +void LMICeu868_setRx1Params(void) { + u1_t const txdr = LMIC.dndr; + s1_t drOffset; + s1_t candidateDr; + + LMICeulike_setRx1Freq(); + + if ( LMIC.rx1DrOffset <= 5) + drOffset = (s1_t) LMIC.rx1DrOffset; + else + // make a reasonable assumption for unspecified value. + drOffset = 5; + + candidateDr = (s1_t) txdr - drOffset; + if (candidateDr < LORAWAN_DR0) + candidateDr = 0; + else if (candidateDr > LORAWAN_DR7) + candidateDr = LORAWAN_DR7; + + LMIC.dndr = (u1_t) candidateDr; + LMIC.rps = dndr2rps(LMIC.dndr); } void diff --git a/src/lmic/lmic_eu_like.c b/src/lmic/lmic_eu_like.c index 60c3006..982ae49 100755 --- a/src/lmic/lmic_eu_like.c +++ b/src/lmic/lmic_eu_like.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,35 +32,95 @@ #if CFG_LMIC_EU_like -void LMIC_enableSubBand(u1_t band) { +bit_t LMIC_enableSubBand(u1_t band) { LMIC_API_PARAMETER(band); + return 0; } -void LMIC_disableSubBand(u1_t band) { +bit_t LMIC_disableSubBand(u1_t band) { LMIC_API_PARAMETER(band); + return 0; } -void LMIC_disableChannel(u1_t channel) { +bit_t LMIC_disableChannel(u1_t channel) { + u2_t old_chmap = LMIC.channelMap; LMIC.channelFreq[channel] = 0; LMIC.channelDrMap[channel] = 0; - LMIC.channelMap &= ~(1 << channel); + LMIC.channelMap = old_chmap & ~(1 << channel); + return LMIC.channelMap != old_chmap; } // this is a no-op provided for compatibilty -void LMIC_enableChannel(u1_t channel) { +bit_t LMIC_enableChannel(u1_t channel) { LMIC_API_PARAMETER(channel); + return 0; } -u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap) { - // Bad page, disable all channel, enable non-existent - if (chpage != 0 || chmap == 0 || (chmap & ~LMIC.channelMap) != 0) - return 0; // illegal input - for (u1_t chnl = 0; chnlchannelFreq, + LMIC.channelFreq, + sizeof(LMIC.channelFreq) + ); + pStateBuffer->channelMap = LMIC.channelMap; +} + +bit_t LMICeulike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer) { + if (memcmp(pStateBuffer->channelFreq, LMIC.channelFreq, sizeof(LMIC.channelFreq)) != 0) + return 1; + return pStateBuffer->channelMap != LMIC.channelMap; +} + +void LMICeulike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer) { + os_copyMem( + LMIC.channelFreq, + pStateBuffer->channelFreq, + sizeof(LMIC.channelFreq) + ); + LMIC.channelMap = pStateBuffer->channelMap; +} + +void LMICeulike_setRx1Freq(void) { +#if !defined(DISABLE_MCMD_DlChannelReq) + uint32_t dlFreq = LMIC.channelDlFreq[LMIC.txChnl]; + if (dlFreq != 0) + LMIC.freq = dlFreq; +#endif // !DISABLE_MCMD_DlChannelReq +} + +// Class A txDone handling for FSK. +void +LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) { + ostime_t const hsym = us2osticksRound(80); + + // start a little earlier. + delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160); + + // set LMIC.rxtime and LMIC.rxsyms: + LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_RXLEN_FSK), hsym); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + #endif // CFG_LMIC_EU_like diff --git a/src/lmic/lmic_eu_like.h b/src/lmic/lmic_eu_like.h index f147790..c9bbeac 100755 --- a/src/lmic/lmic_eu_like.h +++ b/src/lmic/lmic_eu_like.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,7 +56,8 @@ LMICeulike_isValidBeacon1(const uint8_t *d) { #define LMICbandplan_isFSK() (0) // provide a default LMICbandplan_txDoneDoFSK() -#define LMICbandplan_txDoneFSK(delay, func) do { } while (0) +void LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func); +#define LMICbandplan_txDoneFSK(delay, func) LMICeulike_txDoneFSK(delay, func) #define LMICbandplan_joinAcceptChannelClear() LMICbandplan_initDefaultChannels(/* normal, not join */ 0) @@ -74,14 +75,14 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 }; #define LMICbandplan_setSessionInitDefaultChannels() \ do { LMICbandplan_initDefaultChannels(/* normal, not join */ 0); } while (0) -u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap); +bit_t LMICeulike_canMapChannels(u1_t chpage, u2_t chmap); +#define LMICbandplan_canMapChannels(c, m) LMICeulike_canMapChannels(c, m) + +bit_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap); #define LMICbandplan_mapChannels(c, m) LMICeulike_mapChannels(c, m) void LMICeulike_initJoinLoop(u1_t nDefaultChannels, s1_t adrTxPow); -#define LMICbandplan_setRx1Params() \ - do { /*LMIC.freq/rps remain unchanged*/ } while (0) - void LMICeulike_updateTx(ostime_t txbeg); #define LMICbandplan_updateTx(t) LMICeulike_updateTx(t) @@ -95,4 +96,20 @@ static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) { #define LMICbandplan_init() \ do { /* nothing */ } while (0) +void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_saveAdrState(pState) LMICeulike_saveAdrState(pState) + +bit_t LMICeulike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_compareAdrState(pState) LMICeulike_compareAdrState(pState) + +void LMICeulike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_restoreAdrState(pState) LMICeulike_restoreAdrState(pState) + +// set Rx1 frequency (might be different than uplink). +void LMICeulike_setRx1Freq(void); + +bit_t LMICeulike_isDataRateFeasible(dr_t dr); +#define LMICbandplan_isDataRateFeasible(dr) LMICeulike_isDataRateFeasible(dr) + + #endif // _lmic_eu_like_h_ diff --git a/src/lmic/lmic_in866.c b/src/lmic/lmic_in866.c index ad52134..15916a1 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,21 +49,28 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS }; -static CONST_TABLE(u1_t, maxFrameLens)[] = { 59+5,59+5,59+5,123+5, 230+5, 230+5 }; +static CONST_TABLE(u1_t, maxFrameLens)[] = { + 59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5 +}; uint8_t LMICin866_maxFrameLen(uint8_t dr) { if (dr < LENOF_TABLE(maxFrameLens)) return TABLE_GET_U1(maxFrameLens, dr); - else - return 0xFF; + else + return 0; } static CONST_TABLE(s1_t, TXPOWLEVELS)[] = { - 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0 + 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 }; int8_t LMICin866_pow2dBm(uint8_t mcmd_ladr_p1) { - return TABLE_GET_S1(TXPOWLEVELS, (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT); + uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT; + if (pindex < LENOF_TABLE(TXPOWLEVELS)) { + return TABLE_GET_S1(TXPOWLEVELS, pindex); + } else { + return -128; + } } // only used in this module, but used by variant macro dr2hsym(). @@ -88,8 +95,8 @@ ostime_t LMICin866_dr2hsym(uint8_t dr) { enum { NUM_DEFAULT_CHANNELS = 3 }; static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = { // Default operational frequencies - IN866_F1 | BAND_MILLI, - IN866_F2 | BAND_MILLI, + IN866_F1 | BAND_MILLI, + IN866_F2 | BAND_MILLI, IN866_F3 | BAND_MILLI, }; @@ -98,6 +105,9 @@ void LMICin866_initDefaultChannels(bit_t join) { LMIC_API_PARAMETER(join); os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); +#if !defined(DISABLE_MCMD_DlChannelReq) + os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq)); +#endif // !DISABLE_MCMD_DlChannelReq os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); @@ -125,17 +135,32 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { } 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; + } + bit_t fEnable = (freq != 0); if (chidx >= MAX_CHANNELS) return 0; if (band == -1) { - freq |= BAND_MILLI; + freq = (freq&~3) | BAND_MILLI; } else { if (band > BAND_MILLI) return 0; freq = (freq&~3) | band; } LMIC.channelFreq[chidx] = freq; LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(IN866_DR_SF12, IN866_DR_SF7) : drmap; - LMIC.channelMap |= 1 << chidx; // enabled right away + if (fEnable) + LMIC.channelMap |= 1 << chidx; // enabled right away + else + LMIC.channelMap &= ~(1 << chidx); return 1; } @@ -169,7 +194,7 @@ ostime_t LMICin866_nextTx(ostime_t now) { } } - // no enabled channel found! just use the last channel. + // no enabled channel found! just use the last channel. return now; } @@ -187,12 +212,27 @@ ostime_t LMICin866_nextJoinState(void) { } #endif // !DISABLE_JOIN -// txDone handling for FSK. -void -LMICin866_txDoneFSK(ostime_t delay, osjobcb_t func) { - LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); - LMIC.rxsyms = RXLEN_FSK; - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +// set the Rx1 dndr, rps. +void LMICin866_setRx1Params(void) { + u1_t const txdr = LMIC.dndr; + s1_t drOffset; + s1_t candidateDr; + + LMICeulike_setRx1Freq(); + + if ( LMIC.rx1DrOffset <= 5) + drOffset = (s1_t) LMIC.rx1DrOffset; + else + drOffset = 5 - (s1_t) LMIC.rx1DrOffset; + + candidateDr = (s1_t) txdr - drOffset; + if (candidateDr < LORAWAN_DR0) + candidateDr = 0; + else if (candidateDr > LORAWAN_DR5) + candidateDr = LORAWAN_DR5; + + LMIC.dndr = (u1_t) candidateDr; + LMIC.rps = dndr2rps(LMIC.dndr); } void diff --git a/src/lmic/lmic_kr920.c b/src/lmic/lmic_kr920.c new file mode 100755 index 0000000..9ea1dac --- /dev/null +++ b/src/lmic/lmic_kr920.c @@ -0,0 +1,274 @@ +/* +* Copyright (c) 2014-2016 IBM Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define LMIC_DR_LEGACY 0 + +#include "lmic_bandplan.h" + +#if defined(CFG_kr920) +// ================================================================================ +// +// BEG: KR920 related stuff +// + +CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { + ILLEGAL_RPS, + (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0), // [0] + (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0), // [1] + (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0), // [2] + (u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0), // [3] + (u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0), // [4] + (u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0), // [5] + ILLEGAL_RPS, // [6] +}; + +static CONST_TABLE(u1_t, maxFrameLens)[] = { + 59+5, 59+5, 59+5, 123+5, 250+5, 250+5 +}; + +uint8_t LMICkr920_maxFrameLen(uint8_t dr) { + if (dr < LENOF_TABLE(maxFrameLens)) + return TABLE_GET_U1(maxFrameLens, dr); + else + return 0; +} + +static CONST_TABLE(s1_t, TXPOWLEVELS)[] = { + 14, 12, 10, 8, 6, 4, 2, 0 +}; + +int8_t LMICkr920_pow2dBm(uint8_t mcmd_ladr_p1) { + uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT; + if (pindex < LENOF_TABLE(TXPOWLEVELS)) { + return TABLE_GET_S1(TXPOWLEVELS, pindex); + } else { + return -128; + } +} + +// only used in this module, but used by variant macro dr2hsym(). +static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { + us2osticksRound(128 << 7), // DR_SF12 + us2osticksRound(128 << 6), // DR_SF11 + us2osticksRound(128 << 5), // DR_SF10 + us2osticksRound(128 << 4), // DR_SF9 + us2osticksRound(128 << 3), // DR_SF8 + us2osticksRound(128 << 2), // DR_SF7 +}; + +ostime_t LMICkr920_dr2hsym(uint8_t dr) { + return TABLE_GET_OSTIME(DR2HSYM_osticks, dr); +} + + +// All frequencies are marked as BAND_MILLI, and we don't do duty-cycle. But this lets +// us reuse code. +enum { NUM_DEFAULT_CHANNELS = 3 }; +static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = { + // Default operational frequencies + KR920_F1 | BAND_MILLI, + KR920_F2 | BAND_MILLI, + KR920_F3 | BAND_MILLI, +}; + +// korea ignores the join flag, becuase the channel setup is the same either way. +void LMICkr920_initDefaultChannels(bit_t join) { + LMIC_API_PARAMETER(join); + + os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); +#if !defined(DISABLE_MCMD_DlChannelReq) + os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq)); +#endif // !DISABLE_MCMD_DlChannelReq + os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); + os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); + + LMIC.channelMap = (1 << NUM_DEFAULT_CHANNELS) - 1; + for (u1_t fu = 0; fu BAND_MILLI) return 0; + //band_t* b = &LMIC.bands[bandidx]; + xref2band_t b = &LMIC.bands[bandidx]; + b->txpow = txpow; + b->txcap = txcap; + b->avail = os_getTime(); + b->lastchnl = os_getRndU1() % MAX_CHANNELS; + return 1; +} + +bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { + // 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; + } + bit_t fEnable = (freq != 0); + if (chidx >= MAX_CHANNELS) + return 0; + if (band == -1) { + freq = (freq&~3) | BAND_MILLI; + } else { + if (band > BAND_MILLI) return 0; + freq = (freq&~3) | band; + } + LMIC.channelFreq[chidx] = freq; + LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(KR920_DR_SF12, KR920_DR_SF7) : drmap; + if (fEnable) + LMIC.channelMap |= 1 << chidx; // enabled right away + else + LMIC.channelMap &= ~(1 << chidx); + return 1; +} + + + +u4_t LMICkr920_convFreq(xref2cu1_t ptr) { + u4_t freq = (os_rlsbf4(ptr - 1) >> 8) * 100; + if (freq < KR920_FREQ_MIN || freq > KR920_FREQ_MAX) + freq = 0; + 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. +ostime_t LMICkr920_nextTx(ostime_t now) { + const u1_t band = BAND_MILLI; + + 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; + } + } + } + + // no enabled channel found! just use the last channel. + return now; +} + +#if !defined(DISABLE_BEACONS) +void LMICkr920_setBcnRxParams(void) { + LMIC.dataLen = 0; + LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3; + LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN), 1), LEN_BCN); +} +#endif // !DISABLE_BEACONS + +#if !defined(DISABLE_JOIN) +ostime_t LMICkr920_nextJoinState(void) { + return LMICeulike_nextJoinState(NUM_DEFAULT_CHANNELS); +} +#endif // !DISABLE_JOIN + +// set the Rx1 dndr, rps. +void LMICkr920_setRx1Params(void) { + u1_t const txdr = LMIC.dndr; + s1_t drOffset; + s1_t candidateDr; + + LMICeulike_setRx1Freq(); + + if ( LMIC.rx1DrOffset <= 5) + drOffset = (s1_t) LMIC.rx1DrOffset; + else + drOffset = 5 - (s1_t) LMIC.rx1DrOffset; + + candidateDr = (s1_t) txdr - drOffset; + if (candidateDr < LORAWAN_DR0) + candidateDr = 0; + else if (candidateDr > LORAWAN_DR5) + candidateDr = LORAWAN_DR5; + + LMIC.dndr = (u1_t) candidateDr; + LMIC.rps = dndr2rps(LMIC.dndr); +} + +void +LMICkr920_initJoinLoop(void) { + LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ KR920_TX_EIRP_MAX_DBM); +} + +void LMICkr920_updateTx(ostime_t txbeg) { + u4_t freq = LMIC.channelFreq[LMIC.txChnl]; + // Update global/band specific duty cycle stats + ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); + // Update channel/global duty cycle stats + xref2band_t band = &LMIC.bands[freq & 0x3]; + LMIC.freq = freq & ~(u4_t)3; + LMIC.txpow = band->txpow; + if (LMIC.freq <= KR920_FDOWN && LMIC.txpow > KR920_TX_EIRP_MAX_DBM_LOW) { + LMIC.txpow = KR920_TX_EIRP_MAX_DBM_LOW; + } + band->avail = txbeg + airtime * band->txcap; + if (LMIC.globalDutyRate != 0) + LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate); +} + +// +// END: KR920 related stuff +// +// ================================================================================ +#endif diff --git a/src/lmic/lmic_us915.c b/src/lmic/lmic_us915.c index 46f16b6..0d84ac1 100755 --- 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 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,13 +55,26 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { ILLEGAL_RPS // [14] }; -static CONST_TABLE(u1_t, maxFrameLens)[] = { 24,66,142,255,255,255,255,255, 66,142 }; +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 + }; uint8_t LMICus915_maxFrameLen(uint8_t dr) { if (dr < LENOF_TABLE(maxFrameLens)) return TABLE_GET_U1(maxFrameLens, dr); else - return 0xFF; + return 0; +} + +int8_t LMICus915_pow2dbm(uint8_t mcmd_ladr_p1) { + if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) > + ((LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3) + ? US915_LinkAdrReq_POW_MAX_1_0_2 + : US915_LinkAdrReq_POW_MAX_1_0_3)) + return -128; + else + return ((s1_t)(US915_TX_MAX_DBM - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1))); } static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { @@ -98,9 +111,11 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { return 1; } -void LMIC_disableChannel(u1_t channel) { +bit_t LMIC_disableChannel(u1_t channel) { + bit_t result = 0; if (channel < 72 + MAX_XCHANNELS) { if (ENABLED_CHANNEL(channel)) { + result = 1; if (IS_CHANNEL_125khz(channel)) LMIC.activeChannels125khz--; else if (IS_CHANNEL_500khz(channel)) @@ -108,11 +123,14 @@ void LMIC_disableChannel(u1_t channel) { } LMIC.channelMap[channel >> 4] &= ~(1 << (channel & 0xF)); } + return result; } -void LMIC_enableChannel(u1_t channel) { +bit_t LMIC_enableChannel(u1_t channel) { + bit_t result = 0; if (channel < 72 + MAX_XCHANNELS) { if (!ENABLED_CHANNEL(channel)) { + result = 1; if (IS_CHANNEL_125khz(channel)) LMIC.activeChannels125khz++; else if (IS_CHANNEL_500khz(channel)) @@ -120,42 +138,52 @@ void LMIC_enableChannel(u1_t channel) { } LMIC.channelMap[channel >> 4] |= (1 << (channel & 0xF)); } + return result; } -void LMIC_enableSubBand(u1_t band) { +bit_t LMIC_enableSubBand(u1_t band) { ASSERT(band < 8); u1_t start = band * 8; u1_t end = start + 8; + bit_t result = 0; // enable all eight 125 kHz channels in this subband for (int channel = start; channel < end; ++channel) - LMIC_enableChannel(channel); + result |= LMIC_enableChannel(channel); // there's a single 500 kHz channel associated with // each group of 8 125 kHz channels. Enable it, too. - LMIC_enableChannel(64 + band); + result |= LMIC_enableChannel(64 + band); + return result; } -void LMIC_disableSubBand(u1_t band) { + +bit_t LMIC_disableSubBand(u1_t band) { ASSERT(band < 8); u1_t start = band * 8; u1_t end = start + 8; + bit_t result = 0; // disable all eight 125 kHz channels in this subband for (int channel = start; channel < end; ++channel) - LMIC_disableChannel(channel); + result |= LMIC_disableChannel(channel); // there's a single 500 kHz channel associated with // each group of 8 125 kHz channels. Disable it, too. - LMIC_disableChannel(64 + band); + result |= LMIC_disableChannel(64 + band); + return result; } -void LMIC_selectSubBand(u1_t band) { + +bit_t LMIC_selectSubBand(u1_t band) { + bit_t result = 0; + ASSERT(band < 8); for (int b = 0; b<8; ++b) { if (band == b) - LMIC_enableSubBand(b); + result |= LMIC_enableSubBand(b); else - LMIC_disableSubBand(b); + result |= LMIC_disableSubBand(b); } + return result; } void LMICus915_updateTx(ostime_t txbeg) { @@ -193,16 +221,31 @@ void LMICus915_setBcnRxParams(void) { } #endif // !DISABLE_BEACONS -// TODO(tmm@mcci.com): parmeterize for US-like +// set the Rx1 dndr, rps. void LMICus915_setRx1Params(void) { + u1_t const txdr = LMIC.dndr; + u1_t candidateDr; LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; - if( /* TX datarate */LMIC.dndr < US915_DR_SF8C ) - LMIC.dndr += US915_DR_SF10CR - US915_DR_SF10; - else if( LMIC.dndr == US915_DR_SF8C ) - LMIC.dndr = US915_DR_SF7CR; + if ( /* TX datarate */txdr < LORAWAN_DR4) + candidateDr = txdr + 10 - LMIC.rx1DrOffset; + else + candidateDr = LORAWAN_DR13 - LMIC.rx1DrOffset; + + if (candidateDr < LORAWAN_DR8) + candidateDr = LORAWAN_DR8; + else if (candidateDr > LORAWAN_DR13) + candidateDr = LORAWAN_DR13; + + LMIC.dndr = candidateDr; LMIC.rps = dndr2rps(LMIC.dndr); } +void LMICus915_initJoinLoop(void) { + LMICuslike_initJoinLoop(); + + // initialize the adrTxPower. + LMIC.adrTxPow = 20; // dBm +} // // END: US915 related stuff diff --git a/src/lmic/lmic_us_like.c b/src/lmic/lmic_us_like.c index d1ff69b..c6b075f 100755 --- a/src/lmic/lmic_us_like.c +++ b/src/lmic/lmic_us_like.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,31 +91,54 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) { LMIC.activeChannels500khz = 8; } -u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { +// verify that a given setting is permitted +bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap) { /* - || MCMD_LADR_CHP_125ON and MCMD_LADR_CHP_125OFF are special. The + || MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON and MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF are special. The || channel map appllies to 500kHz (ch 64..71) and in addition || all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK || is also special, in that it enables subbands. */ - u1_t base, top; - - if (chpage < MCMD_LADR_CHP_USLIKE_SPECIAL) { - // operate on channels 0..15, 16..31, 32..47, 48..63 - base = chpage << 4; - top = base + 16; - if (base == 64) { + if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) { + // operate on channels 0..15, 16..31, 32..47, 48..63, 64..71 + if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K) { if (chmap & 0xFF00) { // those are reserved bits, fail. return 0; } - top = 72; + } else { + return 1; } - } else if (chpage == MCMD_LADR_CHP_BANK) { - if (chmap & 0xFF00) { - // those are resreved bits, fail. + } else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) { + if (chmap == 0 || (chmap & 0xFF00) != 0) { + // no bits set, or reserved bitsset , fail. return 0; } + } else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON || + chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) { + u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON; + + // if disabling all 125kHz chans, must have at least one 500kHz chan + // don't allow reserved bits to be set in chmap. + if ((! en125 && chmap == 0) || (chmap & 0xFF00) != 0) + return 0; + } else { + return 0; + } + + // if we get here, it looks legal. + return 1; +} + +// map channels. return true if configuration looks valid. +bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { + /* + || MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON and MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF are special. The + || channel map appllies to 500kHz (ch 64..71) and in addition + || all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK + || is also special, in that it enables subbands. + */ + if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) { // each bit enables a bank of channels for (u1_t subband = 0; subband < 8; ++subband, chmap >>= 1) { if (chmap & 1) { @@ -123,37 +146,48 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { } else { LMIC_disableSubBand(subband); } - - // don't change any channels below - base = top = 0; } - } else if (chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF) { - u1_t const en125 = chpage == MCMD_LADR_CHP_125ON; + } else { + u1_t base, top; - // enable or disable all 125kHz channels - for (u1_t chnl = 0; chnl < 64; ++chnl) { - if (en125) - LMIC_enableChannel(chnl); - else - LMIC_disableChannel(chnl); - } + if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) { + // operate on channels 0..15, 16..31, 32..47, 48..63 + // note that the chpage hasn't been shifted right, so + // it's really the base. + base = chpage; + top = base + 16; + if (base == 64) { + top = 72; + } + } else /* if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON || + chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) */ { + u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON; - // then apply mask to top 8 channels. - base = 64; - top = 72; - } else { - return 0; - } + // enable or disable all 125kHz channels + for (u1_t chnl = 0; chnl < 64; ++chnl) { + if (en125) + LMIC_enableChannel(chnl); + else + LMIC_disableChannel(chnl); + } - // apply chmap to channels in [base..top-1]. - // Use enable/disable channel to keep activeChannel counts in sync. - for (u1_t chnl = base; chnl < top; ++chnl, chmap >>= 1) { - if (chmap & 0x0001) - LMIC_enableChannel(chnl); - else - LMIC_disableChannel(chnl); + // then apply mask to top 8 channels. + base = 64; + top = 72; + } + + // apply chmap to channels in [base..top-1]. + // Use enable/disable channel to keep activeChannel counts in sync. + for (u1_t chnl = base; chnl < top; ++chnl, chmap >>= 1) { + if (chmap & 0x0001) + LMIC_enableChannel(chnl); + else + LMIC_disableChannel(chnl); + } } - return 1; + + LMICOS_logEventUint32("LMICuslike_mapChannels", (LMIC.activeChannels125khz << 16u)|(LMIC.activeChannels500khz << 0u)); + return (LMIC.activeChannels125khz > 0) || (LMIC.activeChannels500khz > 0); } // US does not have duty cycling - return now as earliest TX time @@ -161,16 +195,38 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { ostime_t LMICuslike_nextTx(ostime_t now) { // TODO(tmm@mcci.com): use a static const for US-like if (LMIC.datarate >= LMICuslike_getFirst500kHzDR()) { // 500kHz - ASSERT(LMIC.activeChannels500khz>0); - setNextChannel(64, 64 + 8, LMIC.activeChannels500khz); + if (LMIC.activeChannels500khz > 0) { + setNextChannel(64, 64 + 8, LMIC.activeChannels500khz); + } else if (LMIC.activeChannels125khz > 0) { + LMIC.datarate = lowerDR(LMICuslike_getFirst500kHzDR(), 1); + setNextChannel(0, 64, LMIC.activeChannels125khz); + LMICOS_logEvent("LMICuslike_nextTx: no 500k, choose 125k"); + } else { + LMICOS_logEvent("LMICuslike_nextTx: no channels at all (500)"); + } } else { // 125kHz - ASSERT(LMIC.activeChannels125khz>0); - setNextChannel(0, 64, LMIC.activeChannels125khz); + if (LMIC.activeChannels125khz > 0) { + setNextChannel(0, 64, LMIC.activeChannels125khz); + } else if (LMIC.activeChannels500khz > 0) { + LMIC.datarate = LMICuslike_getFirst500kHzDR(); + setNextChannel(64, 64 + 8, LMIC.activeChannels500khz); + LMICOS_logEvent("LMICuslike_nextTx: no 125k, choose 500k"); + } else { + LMICOS_logEvent("LMICuslike_nextTx: no channels at all (125)"); + } } return now; } +bit_t LMICuslike_isDataRateFeasible(dr_t dr) { + if (dr >= LMICuslike_getFirst500kHzDR()) { // 500kHz + return LMIC.activeChannels500khz > 0; + } else { + return LMIC.activeChannels125khz > 6; + } +} + #if !defined(DISABLE_JOIN) void LMICuslike_initJoinLoop(void) { // set an initial condition so that setNextChannel()'s preconds are met @@ -183,18 +239,13 @@ void LMICuslike_initJoinLoop(void) { // starting point. setNextChannel(0, 64, LMIC.activeChannels125khz); - // initialize the adrTxPower. - // TODO(tmm@mcci.com): is this right for all US-like regions - LMIC.adrTxPow = 20; // dBm - ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0); - // make sure LMIC.txend is valid. LMIC.txend = os_getTime(); + ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0); - // make sure the datarate is set to DR0 per LoRaWAN regional reqts V1.0.2, - // section 2.2.2 - // TODO(tmm@mcci.com): parameterize this for US-like - LMICcore_setDrJoin(DRCHG_SET, LORAWAN_DR0); + // make sure the datarate is set to DR2 per LoRaWAN regional reqts V1.0.2, + // section 2.*.2 + LMICcore_setDrJoin(DRCHG_SET, LMICbandplan_getInitialDrJoin()); // TODO(tmm@mcci.com) need to implement the transmit randomization and // duty cycle restrictions from LoRaWAN V1.0.2 section 7. @@ -233,7 +284,7 @@ ostime_t LMICuslike_nextJoinState(void) { setNextChannel(0, 64, LMIC.activeChannels125khz); // TODO(tmm@mcci.com) parameterize - s1_t dr = LORAWAN_DR0; + s1_t dr = LMICuslike_getJoin125kHzDR(); if ((++LMIC.txCnt & 0x7) == 0) { failed = 1; // All DR exhausted - signal failed } @@ -260,4 +311,29 @@ ostime_t LMICuslike_nextJoinState(void) { } #endif +void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) { + os_copyMem( + pStateBuffer->channelMap, + LMIC.channelMap, + sizeof(LMIC.channelMap) + ); + pStateBuffer->activeChannels125khz = LMIC.activeChannels125khz; + pStateBuffer->activeChannels500khz = LMIC.activeChannels500khz; +} + +void LMICuslike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer) { + os_copyMem( + LMIC.channelMap, + pStateBuffer->channelMap, + sizeof(LMIC.channelMap) + ); + LMIC.activeChannels125khz = pStateBuffer->activeChannels125khz; + LMIC.activeChannels500khz = pStateBuffer->activeChannels500khz; +} + + +bit_t LMICuslike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer) { + return memcmp(pStateBuffer->channelMap, LMIC.channelMap, sizeof(LMIC.channelMap)) != 0; +} + #endif // CFG_LMIC_US_like diff --git a/src/lmic/lmic_us_like.h b/src/lmic/lmic_us_like.h index 66bc549..6281236 100755 --- a/src/lmic/lmic_us_like.h +++ b/src/lmic/lmic_us_like.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. -* Copyright (c) 2017 MCCI Corporation. +* Copyright (c) 2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,9 @@ #define IS_CHANNEL_500khz(c) (c>=64 && c<72) #define ENABLED_CHANNEL(chnl) ((LMIC.channelMap[(chnl >> 4)] & (1<<(chnl & 0x0F))) != 0) +// library functions: called from bandplan +void LMICuslike_initJoinLoop(void); + // provide the isValidBeacon1 function -- int for bool. static inline int LMICuslike_isValidBeacon1(const uint8_t *d) { @@ -66,7 +69,7 @@ LMICuslike_isValidBeacon1(const uint8_t *d) { #define LMICbandplan_advanceBeaconChannel() \ do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0) -// TODO(tmm@mcci.com): decide whether we want to do this on every +// TODO(tmm@mcci.com): decide whether we want to do this on every // reset or just restore the last sub-band selected by the user. #define LMICbandplan_resetDefaultChannels() \ LMICbandplan_initDefaultChannels(/* normal */ 0) @@ -77,24 +80,36 @@ void LMICuslike_initDefaultChannels(bit_t fJoin); #define LMICbandplan_setSessionInitDefaultChannels() \ do { /* nothing */} while (0) -u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap); +bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap); +#define LMICbandplan_canMapChannels(chpage, chmap) LMICuslike_canMapChannels(chpage, chmap) + +bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap); #define LMICbandplan_mapChannels(chpage, chmap) LMICuslike_mapChannels(chpage, chmap) ostime_t LMICuslike_nextTx(ostime_t now); #define LMICbandplan_nextTx(now) LMICuslike_nextTx(now) -void LMICuslike_initJoinLoop(void); -#define LMICbandplan_initJoinLoop() LMICuslike_initJoinLoop() - ostime_t LMICuslike_nextJoinState(void); #define LMICbandplan_nextJoinState() LMICuslike_nextJoinState(); -static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) { +static inline ostime_t LMICuslike_nextJoinTime(ostime_t now) { return now; } -#define LMICbandplan_nextJoinTime(now) LMICeulike_nextJoinTime(now) +#define LMICbandplan_nextJoinTime(now) LMICuslike_nextJoinTime(now) #define LMICbandplan_init() \ do { /* nothing */ } while (0) +void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_saveAdrState(pState) LMICuslike_saveAdrState(pState) + +bit_t LMICuslike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_compareAdrState(pState) LMICuslike_compareAdrState(pState) + +void LMICuslike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer); +#define LMICbandplan_restoreAdrState(pState) LMICuslike_restoreAdrState(pState) + +bit_t LMICuslike_isDataRateFeasible(dr_t dr); +#define LMICbandplan_isDataRateFeasible(dr) LMICuslike_isDataRateFeasible(dr) + #endif // _lmic_us_like_h_ diff --git a/src/lmic/lorabase.h b/src/lmic/lorabase.h index 656ea2c..873658b 100755 --- a/src/lmic/lorabase.h +++ b/src/lmic/lorabase.h @@ -44,7 +44,14 @@ typedef u1_t cr_t; typedef u1_t sf_t; typedef u1_t bw_t; typedef u1_t dr_t; + // Radio parameter set (encodes SF/BW/CR/IH/NOCRC) +// 2..0: Spreading factor +// 4..3: bandwidth: 0 == 125kHz, 1 == 250 kHz, 2 == 500 kHz. 3 == reserved. +// 6..5: coding rate: 0 == 4/5, 1 == 4/6, 2 == 4/7, 3 == 4/8 +// 7: nocrc: 0 == with crc, 1 == without crc +// 15..8: Implicit header control: 0 ==> none, 1..0xFF ==> length in bytes. + typedef u2_t rps_t; TYPEDEF_xref2rps_t; @@ -52,7 +59,7 @@ enum { ILLEGAL_RPS = 0xFF }; // Global maximum frame length enum { STD_PREAMBLE_LEN = 8 }; -enum { MAX_LEN_FRAME = 64 }; +enum { MAX_LEN_FRAME = LMIC_ENABLE_long_messages ? 255 : 64 }; enum { LEN_DEVNONCE = 2 }; enum { LEN_ARTNONCE = 3 }; enum { LEN_NETID = 3 }; @@ -97,6 +104,8 @@ enum _dr_code_t { // post conditions from this block: symbols used by general code that is not // ostensiblly region-specific. // DR_DFLTMIN must be defined as a suitable substititute value if we get a bogus DR +// It is misnamed, it should be the maximum DR (which is the minimum SF) for +// 125 kHz. // DR_PAGE is used only for a non-supported debug system, but should be defined. // CHNL_DNW2 is the channel to be used for RX2 // FREQ_DNW2 is the frequency to be used for RX2 @@ -221,8 +230,8 @@ enum _dr_configured_t { #include "lorabase_au921.h" -// per 2.5.3: not implemented -#define LMIC_ENABLE_TxParamSetupReq 0 +// per 2.5.3: must be implemented +#define LMIC_ENABLE_TxParamSetupReq 1 enum { DR_DFLTMIN = AU921_DR_SF7 }; // DR5 @@ -319,6 +328,51 @@ enum _dr_configured_t { }; # endif // LMIC_DR_LEGACY +#elif defined(CFG_kr920) // ============================================== + +#include "lorabase_kr920.h" + +// per 2.8.3 (1.0.3 2.9.3): is not implemented +#define LMIC_ENABLE_TxParamSetupReq 0 + +enum { DR_DFLTMIN = KR920_DR_SF12 }; // DR2 + // DR_PAGE is a debugging parameter +enum { DR_PAGE = DR_PAGE_KR920 }; + +enum { FREQ_PING = KR920_FBCN }; // default ping freq +enum { DR_PING = KR920_DR_SF9 }; // default ping DR: DR3 +enum { FREQ_DNW2 = KR920_FDOWN }; +enum { DR_DNW2 = KR920_DR_SF12 }; +enum { CHNL_BCN = 11 }; +enum { FREQ_BCN = KR920_FBCN }; +enum { DR_BCN = KR920_DR_SF9 }; +enum { AIRTIME_BCN = 144384 }; // micros +enum { LMIC_REGION_EIRP = KR920_LMIC_REGION_EIRP }; // region uses EIRP + +enum { + // Beacon frame format KR SF9 + OFF_BCN_NETID = 0, + OFF_BCN_TIME = 2, + OFF_BCN_CRC1 = 6, + OFF_BCN_INFO = 8, + OFF_BCN_LAT = 9, + OFF_BCN_LON = 12, + OFF_BCN_CRC2 = 15, + LEN_BCN = 17 +}; + +# if LMIC_DR_LEGACY +enum _dr_configured_t { + DR_SF12 = KR920_DR_SF12, + DR_SF11 = KR920_DR_SF11, + DR_SF10 = KR920_DR_SF10, + DR_SF9 = KR920_DR_SF9, + DR_SF8 = KR920_DR_SF8, + DR_SF7 = KR920_DR_SF7, + DR_NONE = KR920_DR_NONE +}; +# endif // LMIC_DR_LEGACY + #elif defined(CFG_in866) // ============================================== #include "lorabase_in866.h" @@ -414,7 +468,6 @@ enum { HDR_FTYPE_DADN = 0x60, // data (unconfirmed) dn HDR_FTYPE_DCUP = 0x80, // data confirmed up HDR_FTYPE_DCDN = 0xA0, // data confirmed dn - HDR_FTYPE_REJOIN = 0xC0, // rejoin for roaming HDR_FTYPE_PROP = 0xE0 }; enum { @@ -422,16 +475,21 @@ enum { }; enum { // Bitfields in frame control octet - FCT_ADREN = 0x80, - FCT_ADRARQ = 0x40, - FCT_ACK = 0x20, - FCT_MORE = 0x10, // also in DN direction: Class B indicator - FCT_OPTLEN = 0x0F, + FCT_ADREN = 0x80, + FCT_ADRACKReq = 0x40, + FCT_ACK = 0x20, + FCT_MORE = 0x10, // also in DN direction: Class B indicator + FCT_OPTLEN = 0x0F, }; enum { // In UP direction: signals class B enabled FCT_CLASSB = FCT_MORE }; + +enum { + LWAN_FCtrl_FOptsLen_MAX = 0x0Fu, // maximum size of embedded MAC commands +}; + enum { NWKID_MASK = (int)0xFE000000, NWKID_BITS = 7 @@ -440,65 +498,78 @@ enum { // MAC uplink commands downwlink too enum { // Class A - MCMD_LCHK_REQ = 0x02, // - LinkCheckReq : - - MCMD_LADR_ANS = 0x03, // - LinkADRAnd : u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK - MCMD_DCAP_ANS = 0x04, // - DutyCycleAns : - - MCMD_DN2P_ANS = 0x05, // - RxParamSetupAns : u1:7-2:RFU 1/0:datarate/channel ack - MCMD_DEVS_ANS = 0x06, // - DevStatusAns : u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31) - MCMD_SNCH_ANS = 0x07, // - NewChannelAns : u1: 7-2=RFU, 1/0:DR/freq ACK - MCMD_RXTimingSetupAns = 0x08, // : - - MCMD_TxParamSetupAns = 0x09, // : - - MCMD_DIChannelAns = 0x0A, // : u1: [7-2]:RFU 1:exists 0:OK - MCMD_DeviceTimeReq = 0x0D, + MCMD_LinkCheckReq = 0x02, // - + MCMD_LinkADRAns = 0x03, // u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK + MCMD_DutyCycleAns = 0x04, // - + MCMD_RXParamSetupAns = 0x05, // u1:7-2:RFU 1/0:datarate/channel ack + MCMD_DevStatusAns = 0x06, // u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31) + MCMD_NewChannelAns = 0x07, // u1: 7-2=RFU, 1/0:DR/freq ACK + MCMD_RXTimingSetupAns = 0x08, // - + MCMD_TxParamSetupAns = 0x09, // - + MCMD_DlChannelAns = 0x0A, // u1: [7-2]:RFU 1:exists 0:OK + MCMD_DeviceTimeReq = 0x0D, // - // Class B - MCMD_PING_IND = 0x10, // - pingability indic : u1: 7=RFU, 6-4:interval, 3-0:datarate - MCMD_PING_ANS = 0x11, // - ack ping freq : u1: 7-1:RFU, 0:freq ok - MCMD_BCNI_REQ = 0x12, // - next beacon start : - + MCMD_PingSlotInfoReq = 0x10, // u1: 7=RFU, 6-4:interval, 3-0:datarate + MCMD_PingSlotChannelAns = 0x11, // u1: 7-1:RFU, 0:freq ok + MCMD_BeaconInfoReq = 0x12, // - (DEPRECATED) + MCMD_BeaconFreqAns = 0x13, // u1: 7-1:RFU, 0:freq ok }; // MAC downlink commands enum { // Class A - MCMD_LCHK_ANS = 0x02, // LinkCheckAns : u1:margin 0-254,255=unknown margin / u1:gwcnt LinkCheckReq - MCMD_LADR_REQ = 0x03, // LinkADRReq : u1:DR/TXPow, u2:chmask, u1:chpage/repeat - MCMD_DCAP_REQ = 0x04, // DutyCycleReq : u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k - MCMD_DN2P_SET = 0x05, // RXParamSetupReq : u1:7-4:RFU/3-0:datarate, u3:freq - MCMD_DEVS_REQ = 0x06, // DevStatusReq : - - MCMD_SNCH_REQ = 0x07, // NewChannelReq : u1:chidx, u3:freq, u1:DRrange - MCMD_RXTimingSetupReq = 0x08, // : u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1) - MCMD_TxParamSetupReq = 0x09, // : u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP - MCMD_DIChannelReq = 0x0A, // : u1: channel, u3: frequency - MCMD_DeviceTimeAns = 0x0D, + MCMD_LinkCheckAns = 0x02, // u1:margin 0-254,255=unknown margin / u1:gwcnt LinkCheckReq + MCMD_LinkADRReq = 0x03, // u1:DR/TXPow, u2:chmask, u1:chpage/repeat + MCMD_DutyCycleReq = 0x04, // u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k + MCMD_RXParamSetupReq = 0x05, // u1:7-4:RFU/3-0:datarate, u3:freq + MCMD_DevStatusReq = 0x06, // - + MCMD_NewChannelReq = 0x07, // u1:chidx, u3:freq, u1:DRrange + MCMD_RXTimingSetupReq = 0x08, // u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1) + MCMD_TxParamSetupReq = 0x09, // u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP + MCMD_DlChannelReq = 0x0A, // u1: channel, u3: frequency + MCMD_DeviceTimeAns = 0x0D, // u4: seconds since epoch, u1: fractional second // Class B - MCMD_PING_SET = 0x11, // set ping freq : u3: freq - MCMD_BCNI_ANS = 0x12, // next beacon start : u2: delay(in TUNIT millis), u1:channel + MCMD_PingSlotInfoAns = 0x10, // - + MCMD_PingSlotChannelReq = 0x11, // u3: freq, u1:dr [7-4]:RFU [3:0]:datarate + MCMD_BeaconTimingAns = 0x12, // u2: delay(in TUNIT millis), u1:channel (DEPRECATED) + MCMD_BeaconFreqReq = 0x13, // u3: freq }; enum { - MCMD_BCNI_TUNIT = 30 // time unit of delay value in millis + MCMD_BeaconTimingAns_TUNIT = 30 // time unit of delay value in millis }; enum { - MCMD_LADR_ANS_RFU = 0xF8, // RFU bits - MCMD_LADR_ANS_POWACK = 0x04, // 0=not supported power level - MCMD_LADR_ANS_DRACK = 0x02, // 0=unknown data rate - MCMD_LADR_ANS_CHACK = 0x01, // 0=unknown channel enabled + MCMD_LinkADRAns_RFU = 0xF8, // RFU bits + MCMD_LinkADRAns_PowerACK = 0x04, // 0=not supported power level + MCMD_LinkADRAns_DataRateACK = 0x02, // 0=unknown data rate + MCMD_LinkADRAns_ChannelACK = 0x01, // 0=unknown channel enabled }; enum { - MCMD_DN2P_ANS_RFU = 0xF8, // RFU bits - MCMD_DN2P_ANS_RX1DrOffsetAck = 0x04, // 0=dr2 not allowed - MCMD_DN2P_ANS_DRACK = 0x02, // 0=unknown data rate - MCMD_DN2P_ANS_CHACK = 0x01, // 0=unknown channel enabled + MCMD_RXParamSetupAns_RFU = 0xF8, // RFU bits + MCMD_RXParamSetupAns_RX1DrOffsetAck = 0x04, // 0=dr2 not allowed + MCMD_RXParamSetupAns_RX2DataRateACK = 0x02, // 0=unknown data rate + MCMD_RXParamSetupAns_ChannelACK = 0x01, // 0=unknown channel enabled }; enum { - MCMD_SNCH_ANS_RFU = 0xFC, // RFU bits - MCMD_SNCH_ANS_DRACK = 0x02, // 0=unknown data rate - MCMD_SNCH_ANS_FQACK = 0x01, // 0=rejected channel frequency + MCMD_NewChannelAns_RFU = 0xFC, // RFU bits + MCMD_NewChannelAns_DataRateACK = 0x02, // 0=unknown data rate + MCMD_NewChannelAns_ChannelACK = 0x01, // 0=rejected channel frequency }; enum { - MCMD_PING_ANS_RFU = 0xFE, - MCMD_PING_ANS_FQACK = 0x01 + MCMD_RXTimingSetupReq_RFU = 0xF0, // RFU bits + MCMD_RXTimingSetupReq_Delay = 0x0F, // delay in secs, 1..15; 0 is mapped to 1. +}; +enum { + MCMD_DlChannelAns_RFU = 0xFC, // RFU bits + MCMD_DlChannelAns_FreqACK = 0x02, // 0 = uplink frequency not defined for this channel + MCMD_DlChannelAns_ChannelACK = 0x01, // 0 = rejected channel freq +}; +enum { + MCMD_PingSlotFreqAns_RFU = 0xFC, + MCMD_PingSlotFreqAns_DataRateACK = 0x02, + MCMD_PingSlotFreqAns_ChannelACK = 0x01, }; enum { @@ -508,65 +579,30 @@ enum { MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level }; -// Bit fields byte#3 of MCMD_LADR_REQ payload +// Bit fields byte#3 of MCMD_LinkADRReq payload enum { - MCMD_LADR_CHP_USLIKE_SPECIAL = 0x50, // first special for us-like - MCMD_LADR_CHP_BANK = 0x50, // special: bits are banks. - MCMD_LADR_CHP_125ON = 0x60, // special channel page enable, bits applied to 64..71 - MCMD_LADR_CHP_125OFF = 0x70, // special channel page: disble 125K, bits apply to 64..71 - MCMD_LADR_N3RFU_MASK = 0x80, - MCMD_LADR_CHPAGE_MASK = 0xF0, - MCMD_LADR_REPEAT_MASK = 0x0F, - MCMD_LADR_REPEAT_1 = 0x01, - MCMD_LADR_CHPAGE_1 = 0x10 + MCMD_LinkADRReq_Redundancy_RFU = 0x80, + MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70, + MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F, + + MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT = 0x00, // direct masking for EU + MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, // EU: enable everything. + + MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K = 0x40, // mask is for the 8 us-like 500 kHz channels + MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL = 0x50, // first special for us-like + MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, // special: bits are banks. + MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, // special channel page enable, bits applied to 64..71 + MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF = 0x70, // special channel page: disble 125K, bits apply to 64..71 + + MCMD_LinkADRReq_ChMaskCntl_CN470_ALL_ON = 0x60, // turn all on for China. }; -// Bit fields byte#0 of MCMD_LADR_REQ payload + +// Bit fields byte#0 of MCMD_LinkADRReq payload enum { - MCMD_LADR_DR_MASK = 0xF0, - MCMD_LADR_POW_MASK = 0x0F, - MCMD_LADR_DR_SHIFT = 4, - MCMD_LADR_POW_SHIFT = 0, -#if defined(CFG_eu868) // TODO(tmm@mcci.com): complete refactor. - EU868_MCMD_LADR_SF12 = EU868_DR_SF12<<4, - EU868_MCMD_LADR_SF11 = EU868_DR_SF11<<4, - EU868_MCMD_LADR_SF10 = EU868_DR_SF10<<4, - EU868_MCMD_LADR_SF9 = EU868_DR_SF9 <<4, - EU868_MCMD_LADR_SF8 = EU868_DR_SF8 <<4, - EU868_MCMD_LADR_SF7 = EU868_DR_SF7 <<4, - EU868_MCMD_LADR_SF7B = EU868_DR_SF7B<<4, - EU868_MCMD_LADR_FSK = EU868_DR_FSK <<4, - - EU868_MCMD_LADR_20dBm = 0, - EU868_MCMD_LADR_14dBm = 1, - EU868_MCMD_LADR_11dBm = 2, - EU868_MCMD_LADR_8dBm = 3, - EU868_MCMD_LADR_5dBm = 4, - EU868_MCMD_LADR_2dBm = 5, -#elif defined(CFG_us915) - US915_MCMD_LADR_SF10 = US915_DR_SF10<<4, - US915_MCMD_LADR_SF9 = US915_DR_SF9 <<4, - US915_MCMD_LADR_SF8 = US915_DR_SF8 <<4, - US915_MCMD_LADR_SF7 = US915_DR_SF7 <<4, - US915_MCMD_LADR_SF8C = US915_DR_SF8C<<4, - US915_MCMD_LADR_SF12CR = US915_DR_SF12CR<<4, - US915_MCMD_LADR_SF11CR = US915_DR_SF11CR<<4, - US915_MCMD_LADR_SF10CR = US915_DR_SF10CR<<4, - US915_MCMD_LADR_SF9CR = US915_DR_SF9CR<<4, - US915_MCMD_LADR_SF8CR = US915_DR_SF8CR<<4, - US915_MCMD_LADR_SF7CR = US915_DR_SF7CR<<4, - - US915_MCMD_LADR_30dBm = 0, - US915_MCMD_LADR_28dBm = 1, - US915_MCMD_LADR_26dBm = 2, - US915_MCMD_LADR_24dBm = 3, - US915_MCMD_LADR_22dBm = 4, - US915_MCMD_LADR_20dBm = 5, - US915_MCMD_LADR_18dBm = 6, - US915_MCMD_LADR_16dBm = 7, - US915_MCMD_LADR_14dBm = 8, - US915_MCMD_LADR_12dBm = 9, - US915_MCMD_LADR_10dBm = 10 -#endif + MCMD_LinkADRReq_DR_MASK = 0xF0, + MCMD_LinkADRReq_POW_MASK = 0x0F, + MCMD_LinkADRReq_DR_SHIFT = 4, + MCMD_LinkADRReq_POW_SHIFT = 0, }; // bit fields of the TxParam request diff --git a/src/lmic/lorabase_as923.h b/src/lmic/lorabase_as923.h index 9cba8c4..d65bb75 100755 --- a/src/lmic/lorabase_as923.h +++ b/src/lmic/lorabase_as923.h @@ -93,4 +93,13 @@ enum { AS923_V102_TX_CAP = 100 }; // v1.0.2 allows 100% # define AS923_TX_CAP AS923_V102_TX_CAP #endif +// TxParam defaults +enum { + // initial value of UplinkDwellTime before TxParamSetupReq received. + AS923_INITIAL_TxParam_UplinkDwellTime = 1, + // initial value of DownlinkDwellTime before TxParamSetupReq received. + AS923_INITIAL_TxParam_DownlinkDwellTime = 1, + AS923_UPLINK_DWELL_TIME_osticks = sec2osticks(20), +}; + #endif /* _lorabase_as923_h_ */ diff --git a/src/lmic/lorabase_au921.h b/src/lmic/lorabase_au921.h index d4c33b9..7d8a198 100755 --- a/src/lmic/lorabase_au921.h +++ b/src/lmic/lorabase_au921.h @@ -76,6 +76,11 @@ enum { enum { AU921_TX_EIRP_MAX_DBM = 30 // 30 dBm }; +enum { + // initial value of UplinkDwellTime before TxParamSetupReq received. + AU921_INITIAL_TxParam_UplinkDwellTime = 1, + AU921_UPLINK_DWELL_TIME_osticks = sec2osticks(20), +}; enum { DR_PAGE_AU921 = 0x10 * (LMIC_REGION_au921 - 1) }; diff --git a/src/lmic/lorabase_kr920.h b/src/lmic/lorabase_kr920.h new file mode 100755 index 0000000..02fd5a0 --- /dev/null +++ b/src/lmic/lorabase_kr920.h @@ -0,0 +1,84 @@ +/* +* Copyright (c) 2014-2016 IBM Corporation. +* All rights reserved. +* +* Copyright (c) 2017, 2019 MCCI Corporation +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _lorabase_kr920_h_ +#define _lorabase_kr920_h_ + +#ifndef _LMIC_CONFIG_PRECONDITIONS_H_ +# include "lmic_config_preconditions.h" +#endif + +/****************************************************************************\ +| +| Basic definitions for KR920 (always in scope) +| +\****************************************************************************/ + +enum _dr_kr920_t { + KR920_DR_SF12 = 0, // DR0 + KR920_DR_SF11, // DR1 + KR920_DR_SF10, // DR2 + KR920_DR_SF9, // DR3 + KR920_DR_SF8, // DR4 + KR920_DR_SF7, // DR5 + KR920_DR_NONE +}; + +// There is no dwell-time or duty-cycle limitation for IN +// +// max power: 30dBM +// +// freq datarates +enum { + KR920_F1 = 922100000, // SF7-12 (DR0-5) + KR920_F2 = 922300000, // SF7-12 (DR0-5) + KR920_F3 = 922500000, // SF7-12 (DR0-5) + KR920_FBCN = 923100000, // beacon/ping + KR920_F14DBM = 922100000, // Allows 14 dBm (not 10) if >= this. + KR920_FDOWN = 921900000, // RX2 downlink frequency +}; +enum { + KR920_FREQ_MIN = 920900000, + KR920_FREQ_MAX = 923300000 +}; +enum { + KR920_TX_EIRP_MAX_DBM = 14, // 14 dBm for most + KR920_TX_EIRP_MAX_DBM_LOW = 10, // 10 dBm for some +}; +enum { DR_PAGE_KR920 = 0x10 * (LMIC_REGION_kr920 - 1) }; + +enum { KR920_LMIC_REGION_EIRP = 1 }; // region uses EIRP + +enum { KR920_LBT_US = 128 }; // microseconds of LBT time. + +enum { KR920_LBT_DB_MAX = -80 }; // maximum channel strength in dB; if TX + // we measure more than this, we don't tx. + +#endif /* _lorabase_in866_h_ */ \ No newline at end of file diff --git a/src/lmic/lorabase_us915.h b/src/lmic/lorabase_us915.h index 0a77184..ed960c6 100755 --- a/src/lmic/lorabase_us915.h +++ b/src/lmic/lorabase_us915.h @@ -73,10 +73,16 @@ enum { US915_FREQ_MAX = 928000000 }; enum { - US915_TX_MAX_DBM = 30 // 30 dBm (but not EIRP): assumes we're + US915_TX_MAX_DBM = 30 // 30 dBm (but not EIRP): assumes we're // on an 64-channel bandplan. See code // that computes tx power. }; + +enum { + US915_LinkAdrReq_POW_MAX_1_0_2 = 0xA, + US915_LinkAdrReq_POW_MAX_1_0_3 = 0xE, +}; + enum { DR_PAGE_US915 = 0x10 * (LMIC_REGION_us915 - 1) }; enum { US915_LMIC_REGION_EIRP = 0 }; // region doesn't use EIRP, uses tx power diff --git a/src/lmic/lorawan_spec_compliance.h b/src/lmic/lorawan_spec_compliance.h new file mode 100755 index 0000000..e479b24 --- /dev/null +++ b/src/lmic/lorawan_spec_compliance.h @@ -0,0 +1,64 @@ +/* + +Module: lorawan_spec_compliance.h + +Function: + Details from the LoRaWAN specification for compliance. + +Copyright notice and license info: + See LICENSE file accompanying this project. + +Author: + Terry Moore, MCCI Corporation March 2019 + +*/ + +#ifndef _lorawan_spec_COMPLIANCE_H_ /* prevent multiple includes */ +#define _lorawan_spec_COMPLIANCE_H_ + +#ifdef __cplusplus +extern "C"{ +#endif + +enum { + // the port for MAC commands + LORAWAN_PORT_MAC = 0u, + // the first port available for applications + LORAWAN_PORT_USER_MIN = 1u, + // the last port available for applications + LORAWAN_PORT_USER_MAX = 223u, + // the base of the reserved port numbers + LORAWAN_PORT_RESERVED = 224u, + // the port for the compliance protocol + LORAWAN_PORT_COMPLIANCE = LORAWAN_PORT_RESERVED + 0u, +}; + +enum lowawan_compliance_cmd_e { + LORAWAN_COMPLIANCE_CMD_DEACTIVATE = 0u, + LORAWAN_COMPLIANCE_CMD_ACTIVATE = 1u, + LORAWAN_COMPLIANCE_CMD_SET_CONFIRM = 2u, + LORAWAN_COMPLIANCE_CMD_SET_UNCONFIRM = 3u, + LORAWAN_COMPLIANCE_CMD_ECHO = 4u, + LORAWAN_COMPLIANCE_CMD_LINK = 5u, + LORAWAN_COMPLIANCE_CMD_JOIN = 6u, + LORAWAN_COMPLIANCE_CMD_CW = 7u, + LORAWAN_COMPLIANCE_CMD_MFG_BASE = 0x80u, +}; + +typedef unsigned char lorawan_compliance_cmd_t; + +// info on specific commands +enum { + LORAWAN_COMPLIANCE_CMD_ACTIVATE_LEN = 4u, + LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC = 1u, + + // Maximum crypto frame size; although the spec says 18, it + // is also used for testing max packet size. + LORAWAN_COMPLIANCE_CMD_ECHO_LEN_MAX = 242u, +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* _lorawan_spec_COMPLIANCE_H_ */ \ No newline at end of file diff --git a/src/lmic/oslmic.c b/src/lmic/oslmic.c index 0552d91..74b8692 100755 --- a/src/lmic/oslmic.c +++ b/src/lmic/oslmic.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. - * Copyright (c) 2016-2017 MCCI Corporation. + * Copyright (c) 2016-2017, 2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,13 +68,15 @@ static int unlinkjob (osjob_t** pnext, osjob_t* job) { return 0; } +static osjob_t** getJobQueue(osjob_t* job) { + return os_jobIsTimed(job) ? &OS.scheduledjobs : &OS.runnablejobs; +} + // clear scheduled job void os_clearCallback (osjob_t* job) { hal_disableIRQs(); - // if it's not in the scheduled jobs, look in the runnable... - if (! unlinkjob(&OS.scheduledjobs, job)) - unlinkjob(&OS.runnablejobs, job); + unlinkjob(getJobQueue(job), job); hal_enableIRQs(); } @@ -83,11 +85,15 @@ void os_clearCallback (osjob_t* job) { void os_setCallback (osjob_t* job, osjobcb_t cb) { osjob_t** pnext; hal_disableIRQs(); + // remove if job was already queued - unlinkjob(&OS.runnablejobs, job); - // fill-in job - job->func = cb; + unlinkjob(getJobQueue(job), job); + + // fill-in job. Ascending memory order is write-queue friendly job->next = NULL; + job->deadline = 0; + job->func = cb; + // add to end of run queue for(pnext=&OS.runnablejobs; *pnext; pnext=&((*pnext)->next)); *pnext = job; @@ -97,13 +103,21 @@ void os_setCallback (osjob_t* job, osjobcb_t cb) { // schedule timed job void os_setTimedCallback (osjob_t* job, ostime_t time, osjobcb_t cb) { osjob_t** pnext; + + // special case time 0 -- it will be one tick late. + if (time == 0) + time = 1; + hal_disableIRQs(); + // remove if job was already queued - unlinkjob(&OS.scheduledjobs, job); + unlinkjob(getJobQueue(job), job); + // fill-in job + job->next = NULL; job->deadline = time; job->func = cb; - job->next = NULL; + // insert into schedule for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) { if((*pnext)->deadline - time > 0) { // (cmp diff, not abs!) @@ -141,3 +155,13 @@ void os_runloop_once() { j->func(j); } } + +// return true if there are any jobs scheduled within time ticks from now. +// return false if any jobs scheduled are at least time ticks in the future. +bit_t os_queryTimeCriticalJobs(ostime_t time) { + if (OS.scheduledjobs && + OS.scheduledjobs->deadline - os_getTime() < time) + return 1; + else + return 0; +} diff --git a/src/lmic/oslmic.h b/src/lmic/oslmic.h index 7710c1d..f6edae0 100755 --- a/src/lmic/oslmic.h +++ b/src/lmic/oslmic.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. - * Copyright (c) 2018 MCCI Corporation + * Copyright (c) 2018, 2019 MCCI Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -84,8 +84,7 @@ typedef u1_t* xref2u1_t; #define SIZEOFEXPR(x) sizeof(x) -#define ON_LMIC_EVENT(ev) onEvent(ev) -#define DECL_ON_LMIC_EVENT void onEvent(ev_t e) +#define DECL_ON_LMIC_EVENT LMIC_DECLARE_FUNCTION_WEAK(void, onEvent, (ev_t e)) extern u4_t AESAUX[]; extern u4_t AESKEY[]; @@ -120,11 +119,19 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi); //================================================================================ - #ifndef RX_RAMPUP +// RX_RAMPUP specifies the extra time we must allow to set up an RX event due +// to platform issues. It's specified in units of ostime_t. It must reflect +// platform jitter and latency, as well as the speed of the LMIC when running +// on this plaform. #define RX_RAMPUP (us2osticks(2000)) #endif + #ifndef TX_RAMPUP +// TX_RAMPUP specifies the extra time we must allow to set up a TX event) due +// to platform issues. It's specified in units of ostime_t. It must reflect +// platform jitter and latency, as well as the speed of the LMIC when running +// on this plaform. #define TX_RAMPUP (us2osticks(2000)) #endif @@ -149,7 +156,13 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi); struct osjob_t; // fwd decl. -typedef void (*osjobcb_t) (struct osjob_t*); + +//! the function type for osjob_t callbacks +typedef void (osjobcbfn_t)(struct osjob_t*); + +//! the pointer-to-function for osjob_t callbacks +typedef osjobcbfn_t *osjobcb_t; + struct osjob_t { struct osjob_t* next; ostime_t deadline; @@ -157,6 +170,11 @@ struct osjob_t { }; TYPEDEF_xref2osjob_t; +//! determine whether a job is timed or immediate. os_setTimedCallback() +// must treat incoming == 0 as being 1 instead. +static inline int os_jobIsTimed(xref2osjob_t job) { + return (job->deadline != 0); +} #ifndef HAS_os_calls @@ -190,6 +208,10 @@ void os_radio (u1_t mode); #ifndef os_getBattLevel u1_t os_getBattLevel (void); #endif +#ifndef os_queryTimeCriticalJobs +//! Return non-zero if any jobs are scheduled between now and now+time. +bit_t os_queryTimeCriticalJobs(ostime_t time); +#endif #ifndef os_rlsbf4 //! Read 32-bit quantity from given pointer in little endian byte order. @@ -310,6 +332,18 @@ extern xref2u1_t AESaux; u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len); #endif +// ====================================================================== +// Simple logging support. Vanishes unless enabled. + +#if LMIC_ENABLE_event_logging +extern void LMICOS_logEvent(const char *pMessage); +extern void LMICOS_logEventUint32(const char *pMessage, uint32_t datum); +#else // ! LMIC_ENABLE_event_logging +# define LMICOS_logEvent(m) do { ; } while (0) +# define LMICOS_logEventUint32(m, d) do { ; } while (0) +#endif // ! LMIC_ENABLE_event_logging + + LMIC_END_DECLS #endif // _oslmic_h_ diff --git a/src/lmic/radio.c b/src/lmic/radio.c index 5320a1c..c97e3aa 100755 --- a/src/lmic/radio.c +++ b/src/lmic/radio.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2014-2016 IBM Corporation. - * Copyright (c) 2016-2018 MCCI Corporation. + * Copyright (c) 2016-2019 MCCI Corporation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,101 +32,106 @@ // ---------------------------------------- // Registers Mapping +// // -type- 1272 vs 1276 #define RegFifo 0x00 // common -#define RegOpMode 0x01 // common -#define FSKRegBitrateMsb 0x02 -#define FSKRegBitrateLsb 0x03 -#define FSKRegFdevMsb 0x04 -#define FSKRegFdevLsb 0x05 -#define RegFrfMsb 0x06 // common -#define RegFrfMid 0x07 // common -#define RegFrfLsb 0x08 // common -#define RegPaConfig 0x09 // common -#define RegPaRamp 0x0A // common -#define RegOcp 0x0B // common -#define RegLna 0x0C // common -#define FSKRegRxConfig 0x0D +#define RegOpMode 0x01 // common see below +#define FSKRegBitrateMsb 0x02 // - +#define FSKRegBitrateLsb 0x03 // - +#define FSKRegFdevMsb 0x04 // - +#define FSKRegFdevLsb 0x05 // - +#define RegFrfMsb 0x06 // common FSK: 1272: 915; 1276: 434 MHz +#define RegFrfMid 0x07 // common ditto +#define RegFrfLsb 0x08 // common ditto +#define RegPaConfig 0x09 // common see below, many diffs +#define RegPaRamp 0x0A // common see below: bits 6..4 are diff +#define RegOcp 0x0B // common - +#define RegLna 0x0C // common bits 4..0 are diff. +#define FSKRegRxConfig 0x0D // - #define LORARegFifoAddrPtr 0x0D -#define FSKRegRssiConfig 0x0E +#define FSKRegRssiConfig 0x0E // - #define LORARegFifoTxBaseAddr 0x0E -#define FSKRegRssiCollision 0x0F +#define FSKRegRssiCollision 0x0F // - #define LORARegFifoRxBaseAddr 0x0F -#define FSKRegRssiThresh 0x10 +#define FSKRegRssiThresh 0x10 // - #define LORARegFifoRxCurrentAddr 0x10 -#define FSKRegRssiValue 0x11 +#define FSKRegRssiValue 0x11 // - #define LORARegIrqFlagsMask 0x11 -#define FSKRegRxBw 0x12 +#define FSKRegRxBw 0x12 // - #define LORARegIrqFlags 0x12 -#define FSKRegAfcBw 0x13 +#define FSKRegAfcBw 0x13 // - #define LORARegRxNbBytes 0x13 -#define FSKRegOokPeak 0x14 +#define FSKRegOokPeak 0x14 // - #define LORARegRxHeaderCntValueMsb 0x14 -#define FSKRegOokFix 0x15 +#define FSKRegOokFix 0x15 // - #define LORARegRxHeaderCntValueLsb 0x15 -#define FSKRegOokAvg 0x16 +#define FSKRegOokAvg 0x16 // - #define LORARegRxPacketCntValueMsb 0x16 #define LORARegRxpacketCntValueLsb 0x17 #define LORARegModemStat 0x18 #define LORARegPktSnrValue 0x19 -#define FSKRegAfcFei 0x1A +#define FSKRegAfcFei 0x1A // - #define LORARegPktRssiValue 0x1A -#define FSKRegAfcMsb 0x1B +#define FSKRegAfcMsb 0x1B // - #define LORARegRssiValue 0x1B -#define FSKRegAfcLsb 0x1C +#define FSKRegAfcLsb 0x1C // - #define LORARegHopChannel 0x1C -#define FSKRegFeiMsb 0x1D +#define FSKRegFeiMsb 0x1D // - #define LORARegModemConfig1 0x1D -#define FSKRegFeiLsb 0x1E +#define FSKRegFeiLsb 0x1E // - #define LORARegModemConfig2 0x1E -#define FSKRegPreambleDetect 0x1F +#define FSKRegPreambleDetect 0x1F // - #define LORARegSymbTimeoutLsb 0x1F -#define FSKRegRxTimeout1 0x20 +#define FSKRegRxTimeout1 0x20 // - #define LORARegPreambleMsb 0x20 -#define FSKRegRxTimeout2 0x21 +#define FSKRegRxTimeout2 0x21 // - #define LORARegPreambleLsb 0x21 -#define FSKRegRxTimeout3 0x22 +#define FSKRegRxTimeout3 0x22 // - #define LORARegPayloadLength 0x22 -#define FSKRegRxDelay 0x23 +#define FSKRegRxDelay 0x23 // - #define LORARegPayloadMaxLength 0x23 -#define FSKRegOsc 0x24 +#define FSKRegOsc 0x24 // - #define LORARegHopPeriod 0x24 -#define FSKRegPreambleMsb 0x25 +#define FSKRegPreambleMsb 0x25 // - #define LORARegFifoRxByteAddr 0x25 +#define FSKRegPreambleLsb 0x26 // - #define LORARegModemConfig3 0x26 -#define FSKRegPreambleLsb 0x26 -#define FSKRegSyncConfig 0x27 +#define FSKRegSyncConfig 0x27 // - #define LORARegFeiMsb 0x28 -#define FSKRegSyncValue1 0x28 +#define FSKRegSyncValue1 0x28 // - #define LORAFeiMib 0x29 -#define FSKRegSyncValue2 0x29 +#define FSKRegSyncValue2 0x29 // - #define LORARegFeiLsb 0x2A -#define FSKRegSyncValue3 0x2A -#define FSKRegSyncValue4 0x2B +#define FSKRegSyncValue3 0x2A // - +#define FSKRegSyncValue4 0x2B // - #define LORARegRssiWideband 0x2C -#define FSKRegSyncValue5 0x2C -#define FSKRegSyncValue6 0x2D -#define FSKRegSyncValue7 0x2E -#define FSKRegSyncValue8 0x2F -#define FSKRegPacketConfig1 0x30 -#define FSKRegPacketConfig2 0x31 +#define FSKRegSyncValue5 0x2C // - +#define FSKRegSyncValue6 0x2D // - +#define FSKRegSyncValue7 0x2E // - +#define FSKRegSyncValue8 0x2F // - +#define LORARegIffReq1 0x2F +#define FSKRegPacketConfig1 0x30 // - +#define LORARegIffReq2 0x30 +#define FSKRegPacketConfig2 0x31 // - #define LORARegDetectOptimize 0x31 -#define FSKRegPayloadLength 0x32 -#define FSKRegNodeAdrs 0x33 +#define FSKRegPayloadLength 0x32 // - +#define FSKRegNodeAdrs 0x33 // - #define LORARegInvertIQ 0x33 -#define FSKRegBroadcastAdrs 0x34 -#define FSKRegFifoThresh 0x35 -#define FSKRegSeqConfig1 0x36 -#define FSKRegSeqConfig2 0x37 +#define FSKRegBroadcastAdrs 0x34 // - +#define FSKRegFifoThresh 0x35 // - +#define FSKRegSeqConfig1 0x36 // - +#define LORARegHighBwOptimize1 0x36 +#define FSKRegSeqConfig2 0x37 // - #define LORARegDetectionThreshold 0x37 -#define FSKRegTimerResol 0x38 -#define FSKRegTimer1Coef 0x39 +#define FSKRegTimerResol 0x38 // - +#define FSKRegTimer1Coef 0x39 // - #define LORARegSyncWord 0x39 -#define FSKRegTimer2Coef 0x3A -#define FSKRegImageCal 0x3B -#define FSKRegTemp 0x3C -#define FSKRegLowBat 0x3D -#define FSKRegIrqFlags1 0x3E -#define FSKRegIrqFlags2 0x3F +#define FSKRegTimer2Coef 0x3A // - +#define LORARegHighBwOptimize2 0x3A +#define FSKRegImageCal 0x3B // - +#define FSKRegTemp 0x3C // - +#define FSKRegLowBat 0x3D // - +#define FSKRegIrqFlags1 0x3E // - +#define FSKRegIrqFlags2 0x3F // - #define RegDioMapping1 0x40 // common #define RegDioMapping2 0x41 // common #define RegVersion 0x42 // common @@ -142,8 +147,8 @@ // #define RegBitRateFrac 0x70 // common #if defined(CFG_sx1276_radio) -#define RegTcxo 0x4B // common -#define RegPaDac 0x4D // common +#define RegTcxo 0x4B // common different addresses, same bits +#define RegPaDac 0x4D // common differnet addresses, same bits #elif defined(CFG_sx1272_radio) #define RegTcxo 0x58 // common #define RegPaDac 0x5A // common @@ -188,6 +193,34 @@ #define SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01 +#ifdef CFG_sx1276_radio +# define SX127X_MC1_IMPLICIT_HEADER_MODE_ON SX1276_MC1_IMPLICIT_HEADER_MODE_ON +#else +# define SX127X_MC1_IMPLICIT_HEADER_MODE_ON SX1272_MC1_IMPLICIT_HEADER_MODE_ON +#endif + +// transmit power configuration for RegPaConfig +#define SX1276_PAC_PA_SELECT_PA_BOOST 0x80 +#define SX1276_PAC_PA_SELECT_RFIO_PIN 0x00 +#define SX1276_PAC_MAX_POWER_MASK 0x70 + +// the bits to change for max power. +#define SX127X_PADAC_POWER_MASK 0x07 +#define SX127X_PADAC_POWER_NORMAL 0x04 +#define SX127X_PADAC_POWER_20dBm 0x07 + +// convert milliamperes to equivalent value for +// RegOcp; delivers conservative value. +#define SX127X_OCP_MAtoBITS(mA) \ + ((mA) < 45 ? 0 : \ + (mA) <= 120 ? ((mA) - 45) / 5 : \ + (mA) < 130 ? 0xF : \ + (mA) < 240 ? ((mA) - 130) / 10 + 0x10 : \ + 27) + +// bit in RegOcp that enables overcurrent protect. +#define SX127X_OCP_ENA 0x20 + // sx1276 RegModemConfig2 #define SX1276_MC2_RX_PAYLOAD_CRCON 0x04 @@ -208,10 +241,13 @@ //----------------------------------------- // Parameters for RSSI monitoring #define SX127X_FREQ_LF_MAX 525000000 // per datasheet 6.3 - -// per datasheet 5.5.3: -#define SX127X_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF) -#define SX127X_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF) + +// per datasheet 5.5.3 and 5.5.5: +#define SX1272_RSSI_ADJUST -139 // add to rssi value to get dB (LF) + +// per datasheet 5.5.3 and 5.5.5: +#define SX1276_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF) +#define SX1276_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF) // per datasheet 2.5.2 (but note that we ought to ask Semtech to confirm, because // datasheet is unclear). @@ -230,6 +266,40 @@ #define OPMODE_RX_SINGLE 0x06 #define OPMODE_CAD 0x07 +// ---------------------------------------- +// FSK opmode bits +// bits 6:5 are the same for 1272 and 1276 +#define OPMODE_FSK_SX127x_ModulationType_FSK (0u << 5) +#define OPMODE_FSK_SX127x_ModulationType_OOK (1u << 5) +#define OPMODE_FSK_SX127x_ModulationType_MASK (3u << 5) + +// bits 4:3 are different for 1272 +#define OPMODE_FSK_SX1272_ModulationShaping_FSK_None (0u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT1_0 (1u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_5 (2u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_3 (3u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_OOK_None (0u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_OOK_BR (1u << 3) +#define OPMODE_FSK_SX1272_ModulationShaping_OOK_2BR (2u << 3) + +#define OPMODE_FSK_SX1272_ModulationShaping_MASK (3u << 3) + +// SX1276 +#define OPMODE_FSK_SX1276_LowFrequencyModeOn (1u << 3) + +// define the opmode bits apporpriate for the 127x in use. +#if defined(CFG_sx1272_radio) +# define OPMODE_FSK_SX127X_SETUP (OPMODE_FSK_SX127x_ModulationType_FSK | \ + OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_5) +#elif defined(CFG_sx1276_radio) +# define OPMODE_FSK_SX127X_SETUP (OPMODE_FSK_SX127x_ModulationType_FSK) +#endif + +// ---------------------------------------- +// LoRa opmode bits +#define OPMODE_LORA_SX127x_AccessSharedReg (1u << 6) +#define OPMODE_LORA_SX1276_LowFrequencyModeOn (1u << 3) + // ---------------------------------------- // Bits masking the corresponding IRQs from the radio #define IRQ_LORA_RXTOUT_MASK 0x80 @@ -264,7 +334,7 @@ #define MAP_DIO0_LORA_TXDONE 0x40 // 01------ #define MAP_DIO1_LORA_RXTOUT 0x00 // --00---- #define MAP_DIO1_LORA_NOP 0x30 // --11---- -#define MAP_DIO2_LORA_NOP 0xC0 // ----11-- +#define MAP_DIO2_LORA_NOP 0x0C // ----11-- #define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready) #define MAP_DIO1_FSK_NOP 0x30 // --11---- @@ -283,20 +353,20 @@ #define RF_IMAGECAL_IMAGECAL_RUNNING 0x20 #define RF_IMAGECAL_IMAGECAL_DONE 0x00 // Default - -// RADIO STATE -// (initialized by radio_init(), used by radio_rand1()) -static u1_t randbuf[16]; - - +// LNA gain constant. Bits 4..0 have different meaning for 1272 and 1276, but +// by chance, the bit patterns we use are the same. #ifdef CFG_sx1276_radio -#define LNA_RX_GAIN (0x20|0x1) +#define LNA_RX_GAIN (0x20|0x3) #elif CFG_sx1272_radio #define LNA_RX_GAIN (0x20|0x03) #else #error Missing CFG_sx1272_radio/CFG_sx1276_radio #endif +// RADIO STATE +// (initialized by radio_init(), used by radio_rand1()) +static u1_t randbuf[16]; + static void writeReg (u1_t addr, u1_t data ) { hal_spi_write(addr | 0x80, &data, 1); @@ -339,15 +409,20 @@ static void opmode (u1_t mode) { static void opmodeLora() { u1_t u = OPMODE_LORA; #ifdef CFG_sx1276_radio - u |= 0x8; // TBD: sx1276 high freq + if (LMIC.freq <= SX127X_FREQ_LF_MAX) { + u |= OPMODE_FSK_SX1276_LowFrequencyModeOn; + } #endif writeOpmode(u); } static void opmodeFSK() { - u1_t u = 0; + u1_t u = OPMODE_FSK_SX127X_SETUP; + #ifdef CFG_sx1276_radio - u |= 0x8; // TBD: sx1276 high freq + if (LMIC.freq <= SX127X_FREQ_LF_MAX) { + u |= OPMODE_FSK_SX1276_LowFrequencyModeOn; + } #endif writeOpmode(u); } @@ -359,7 +434,9 @@ static void configLoraModem () { #ifdef CFG_sx1276_radio u1_t mc1 = 0, mc2 = 0, mc3 = 0; - switch (getBw(LMIC.rps)) { + bw_t const bw = getBw(LMIC.rps); + + switch (bw) { case BW125: mc1 |= SX1276_MC1_BW_125; break; case BW250: mc1 |= SX1276_MC1_BW_250; break; case BW500: mc1 |= SX1276_MC1_BW_500; break; @@ -389,10 +466,34 @@ static void configLoraModem () { writeReg(LORARegModemConfig2, mc2); mc3 = SX1276_MC3_AGCAUTO; - if ((sf == SF11 || sf == SF12) && getBw(LMIC.rps) == BW125) { + + if ( ((sf == SF11 || sf == SF12) && bw == BW125) || + ((sf == SF12) && bw == BW250) ) { mc3 |= SX1276_MC3_LOW_DATA_RATE_OPTIMIZE; } writeReg(LORARegModemConfig3, mc3); + + // Errata 2.1: Sensitivity optimization with 500 kHz bandwidth + u1_t rHighBwOptimize1; + u1_t rHighBwOptimize2; + + rHighBwOptimize1 = 0x03; + rHighBwOptimize2 = 0; + + if (bw == BW500) { + if (LMIC.freq > SX127X_FREQ_LF_MAX) { + rHighBwOptimize1 = 0x02; + rHighBwOptimize2 = 0x64; + } else { + rHighBwOptimize1 = 0x02; + rHighBwOptimize2 = 0x7F; + } + } + + writeReg(LORARegHighBwOptimize1, rHighBwOptimize1); + if (rHighBwOptimize2 != 0) + writeReg(LORARegHighBwOptimize2, rHighBwOptimize2); + #elif CFG_sx1272_radio u1_t mc1 = (getBw(LMIC.rps)<<6); @@ -440,69 +541,220 @@ static void configChannel () { writeReg(RegFrfLsb, (u1_t)(frf>> 0)); } - +// On the SX1276, we have several possible configs. +// 1) using RFO, MaxPower==0: in that case power is -4 to 11 dBm +// 2) using RFO, MaxPower==7: in that case, power is 0 to 14 dBm +// (can't select 15 dBm). +// note we can use -4..11 w/o Max and then 12..14 w/Max, and +// we really don't need to ask anybody. +// 3) using PA_BOOST, PaDac = 4: in that case power range is 2 to 17 dBm; +// use this for 15..17 if authorized. +// 4) using PA_BOOST, PaDac = 7, OutputPower=0xF: in that case, power is 20 dBm +// (and perhaps 0xE is 19, 0xD is 18 dBm, but datasheet isn't clear.) +// and duty cycle must be <= 1%. +// +// In addition, there are some boards for which PA_BOOST can only be used if the +// channel frequency is greater than SX127X_FREQ_LF_MAX. +// +// The SX1272 is similar but has no MaxPower bit: +// 1) using RFO: power is -1 to 13 dBm (datasheet implies max OutputPower value is 14 for 13 dBm) +// 2) using PA_BOOST, PaDac = 0x84: power is 2 to 17 dBm; +// use this for 14..17 if authorized +// 3) using PA_BOOST, PaDac = 0x87, OutputPower = 0xF: power is 20dBm +// and duty cycle must be <= 1% +// +// The general policy is to use the lowest power variant that will get us where we +// need to be. +// static void configPower () { + // our input paramter -- might be different than LMIC.txpow! + s1_t const req_pw = (s1_t)LMIC.radio_txpow; + // the effective power + s1_t eff_pw; + // the policy; we're going to compute this. + u1_t policy; + // what we'll write to RegPaConfig + u1_t rPaConfig; + // what we'll write to RegPaDac + u1_t rPaDac; + // what we'll write to RegOcp + u1_t rOcp; + #ifdef CFG_sx1276_radio - // PA_BOOST output is assumed but not 20 dBm. - s1_t pw = (s1_t)LMIC.txpow; - if(pw > 17) { - pw = 17; - } else if(pw < 2) { - pw = 2; + if (req_pw >= 20) { + policy = LMICHAL_radio_tx_power_policy_20dBm; + eff_pw = 20; + } else if (req_pw >= 14) { + policy = LMICHAL_radio_tx_power_policy_paboost; + if (req_pw > 17) { + eff_pw = 17; + } else { + eff_pw = req_pw; + } + } else { + policy = LMICHAL_radio_tx_power_policy_rfo; + if (req_pw < -4) { + eff_pw = -4; + } else { + eff_pw = req_pw; + } + } + + policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq); + + switch (policy) { + default: + case LMICHAL_radio_tx_power_policy_rfo: + rPaDac = SX127X_PADAC_POWER_NORMAL; + rOcp = SX127X_OCP_MAtoBITS(80); + + if (eff_pw > 14) + eff_pw = 14; + if (eff_pw > 11) { + // some Semtech code uses this down to eff_pw == 0. + rPaConfig = eff_pw | SX1276_PAC_MAX_POWER_MASK; + } else { + if (eff_pw < -4) + eff_pw = -4; + rPaConfig = eff_pw + 4; + } + break; + + // some radios (HopeRF RFM95W) don't support RFO well, + // so the policy might *raise* rfo to paboost. That means + // we have to re-check eff_pw, which might be too small. + // (And, of course, it might also be too large.) + case LMICHAL_radio_tx_power_policy_paboost: + // It seems that SX127x doesn't like eff_pw 10 when in FSK mode. + if (getSf(LMIC.rps) == FSK && eff_pw < 11) { + eff_pw = 11; + } + rPaDac = SX127X_PADAC_POWER_NORMAL; + rOcp = SX127X_OCP_MAtoBITS(100); + if (eff_pw > 17) + eff_pw = 17; + else if (eff_pw < 2) + eff_pw = 2; + rPaConfig = (eff_pw - 2) | SX1276_PAC_PA_SELECT_PA_BOOST; + break; + + case LMICHAL_radio_tx_power_policy_20dBm: + rPaDac = SX127X_PADAC_POWER_20dBm; + rOcp = SX127X_OCP_MAtoBITS(130); + rPaConfig = 0xF | SX1276_PAC_PA_SELECT_PA_BOOST; + break; } - // 0x80 forces use of PA_BOOST; but we don't - // turn on 20 dBm mode. So powers are: - // 0000 => 2dBm, 0001 => 3dBm, ... 1111 => 17dBm - // But we also enforce that the high-power mode - // is off by writing RegPaDac. - writeReg(RegPaConfig, (u1_t)(0x80|(pw - 2))); - writeReg(RegPaDac, readReg(RegPaDac)|0x4); #elif CFG_sx1272_radio - // set PA config (2-17 dBm using PA_BOOST) - s1_t pw = (s1_t)LMIC.txpow; - if(pw > 17) { - pw = 17; - } else if(pw < 2) { - pw = 2; + if (req_pw >= 20) { + policy = LMICHAL_radio_tx_power_policy_20dBm; + eff_pw = 20; + } else if (eff_pw >= 14) { + policy = LMICHAL_radio_tx_power_policy_paboost; + if (eff_pw > 17) { + eff_pw = 17; + } else { + eff_pw = req_pw; + } + } else { + policy = LMICHAL_radio_tx_power_policy_rfo; + if (req_pw < -1) { + eff_pw = -1; + } else { + eff_pw = req_pw; + } + } + + policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq); + + switch (policy) { + default: + case LMICHAL_radio_tx_power_policy_rfo: + rPaDac = SX127X_PADAC_POWER_NORMAL; + rOcp = SX127X_OCP_MAtoBITS(50); + + if (eff_pw > 13) + eff_pw = 13; + + rPaConfig = eff_pw + 1; + break; + + case LMICHAL_radio_tx_power_policy_paboost: + rPaDac = SX127X_PADAC_POWER_NORMAL; + rOcp = SX127X_OCP_MAtoBITS(100); + + if (eff_pw > 17) + eff_pw = 17; + + rPaConfig = (eff_pw - 2) | SX1272_PAC_PA_SELECT_PA_BOOST; + break; + + case LMICHAL_radio_tx_power_policy_20dBm: + rPaDac = SX127X_PADAC_POWER_20dBm; + rOcp = SX127X_OCP_MAtoBITS(130); + + rPaConfig = 0xF | SX1276_PAC_PA_SELECT_PA_BOOST; + break; } - writeReg(RegPaConfig, (u1_t)(0x80|(pw-2))); #else #error Missing CFG_sx1272_radio/CFG_sx1276_radio #endif /* CFG_sx1272_radio */ + + writeReg(RegPaConfig, rPaConfig); + writeReg(RegPaDac, (readReg(RegPaDac) & ~SX127X_PADAC_POWER_MASK) | rPaDac); + writeReg(RegOcp, rOcp | SX127X_OCP_ENA); } -static void txfsk () { - // select FSK modem (from sleep mode) - writeOpmode(0x10); // FSK, BT=0.5 - ASSERT(readReg(RegOpMode) == 0x10); - // enter standby mode (required for FIFO loading)) - opmode(OPMODE_STANDBY); +static void setupFskRxTx(bit_t fDisableAutoClear) { // set bitrate writeReg(FSKRegBitrateMsb, 0x02); // 50kbps writeReg(FSKRegBitrateLsb, 0x80); // set frequency deviation writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz writeReg(FSKRegFdevLsb, 0x99); - // frame and packet handler settings - writeReg(FSKRegPreambleMsb, 0x00); - writeReg(FSKRegPreambleLsb, 0x05); - writeReg(FSKRegSyncConfig, 0x12); - writeReg(FSKRegPacketConfig1, 0xD0); - writeReg(FSKRegPacketConfig2, 0x40); + + // set sync config + writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync + + // set packet config + writeReg(FSKRegPacketConfig1, fDisableAutoClear ? 0xD8 : 0xD0); // var-length, whitening, crc, no auto-clear, no adr filter + writeReg(FSKRegPacketConfig2, 0x40); // packet mode + + // set sync value writeReg(FSKRegSyncValue1, 0xC1); writeReg(FSKRegSyncValue2, 0x94); writeReg(FSKRegSyncValue3, 0xC1); +} + +static void txfsk () { + // select FSK modem (from sleep mode) + opmodeFSK(); + + // enter standby mode (required for FIFO loading)) + opmode(OPMODE_STANDBY); + // set bitrate etc + setupFskRxTx(/* don't autoclear CRC */ 0); + + // frame and packet handler settings + writeReg(FSKRegPreambleMsb, 0x00); + writeReg(FSKRegPreambleLsb, 0x05); + // configure frequency configChannel(); // configure output power configPower(); +#ifdef CFG_sx1276_radio + // select Gausian filter BT=0.5, default ramp. + writeReg(RegPaRamp, 0x29); +#endif + // set the IRQ mapping DIO0=PacketSent DIO1=NOP DIO2=NOP writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TXNOP); // initialize the payload size and address pointers + // TODO(tmm@mcci.com): datasheet says this is not used in variable packet length mode writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload)) // download length byte and buffer to the radio FIFO @@ -529,7 +781,11 @@ static void txlora () { // configure frequency configChannel(); // configure output power +#ifdef CFG_sx1272_radio writeReg(RegPaRamp, (readReg(RegPaRamp) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec +#elif defined(CFG_sx1276_radio) + writeReg(RegPaRamp, 0x08); // set PA ramp-up time 50 uSec, clear FSK bits +#endif configPower(); // set sync word writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE); @@ -587,7 +843,7 @@ static void starttx () { oslmic_radio_rssi_t rssi; radio_monitor_rssi(LMIC.lbt_ticks, &rssi); #if LMIC_X_DEBUG_LEVEL > 0 - LMIC_X_DEBUG_PRINTF("LBT rssi max:min=%d:%d %d times in %d\n", rssi.max_rssi, rssi.min_rssi, rssi.n_rssi, LMIC.lbt_ticks); + LMIC_X_DEBUG_PRINTF("LBT rssi max:min=%d:%d %d times in %d\n", rssi.max_rssi, rssi.min_rssi, rssi.n_rssi, LMIC.lbt_ticks); #endif if (rssi.max_rssi >= LMIC.lbt_dbmax) { @@ -634,8 +890,8 @@ static void rxlora (u1_t rxmode) { // set LNA gain writeReg(RegLna, LNA_RX_GAIN); // set max payload size - writeReg(LORARegPayloadMaxLength, 64); -#if !defined(DISABLE_INVERT_IQ_ON_RX) + writeReg(LORARegPayloadMaxLength, MAX_LEN_FRAME); +#if !defined(DISABLE_INVERT_IQ_ON_RX) /* DEPRECATED(tmm@mcci.com); #250. remove test, always include code in V3 */ // use inverted I/Q signal (prevent mote-to-mote communication) // XXX: use flag to switch on/off inversion @@ -645,6 +901,18 @@ static void rxlora (u1_t rxmode) { writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6)); } #endif + + // Errata 2.3 - receiver spurious reception of a LoRa signal + bw_t const bw = getBw(LMIC.rps); + u1_t const rDetectOptimize = (readReg(LORARegDetectOptimize) & 0x78) | 0x03; + if (bw < BW500) { + writeReg(LORARegDetectOptimize, rDetectOptimize); + writeReg(LORARegIffReq1, 0x40); + writeReg(LORARegIffReq2, 0x40); + } else { + writeReg(LORARegDetectOptimize, rDetectOptimize | 0x80); + } + // set symbol timeout (for single rx) writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms); // set sync word @@ -660,13 +928,16 @@ static void rxlora (u1_t rxmode) { // enable antenna switch for RX hal_pin_rxtx(0); + writeReg(LORARegFifoAddrPtr, 0); + writeReg(LORARegFifoRxBaseAddr, 0); + // now instruct the radio to receive if (rxmode == RXMODE_SINGLE) { // single rx hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time opmode(OPMODE_RX_SINGLE); #if LMIC_DEBUG_LEVEL > 0 - ostime_t now = os_getTime(); - LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime); + ostime_t now = os_getTime(); + LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime); #endif } else { // continous rx (scan or rssi) opmode(OPMODE_RX); @@ -703,33 +974,19 @@ static void rxfsk (u1_t rxmode) { // configure frequency configChannel(); // set LNA gain - //writeReg(RegLna, 0x20|0x03); // max gain, boost enable - writeReg(RegLna, LNA_RX_GAIN); + writeReg(RegLna, LNA_RX_GAIN); // max gain, boost enable. // configure receiver writeReg(FSKRegRxConfig, 0x1E); // AFC auto, AGC, trigger on preamble?!? // set receiver bandwidth - writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb + writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb // set AFC bandwidth - writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB + writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB // set preamble detection writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors - // set sync config - writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync - // set packet config - writeReg(FSKRegPacketConfig1, 0xD8); // var-length, whitening, crc, no auto-clear, no adr filter - writeReg(FSKRegPacketConfig2, 0x40); // packet mode - // set sync value - writeReg(FSKRegSyncValue1, 0xC1); - writeReg(FSKRegSyncValue2, 0x94); - writeReg(FSKRegSyncValue3, 0xC1); // set preamble timeout writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2); - // set bitrate - writeReg(FSKRegBitrateMsb, 0x02); // 50kbps - writeReg(FSKRegBitrateLsb, 0x80); - // set frequency deviation - writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz - writeReg(FSKRegFdevLsb, 0x99); + // set bitrate, autoclear CRC + setupFskRxTx(1); // configure DIO mapping DIO0=PayloadReady DIO1=NOP DIO2=TimeOut writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TIMEOUT); @@ -864,11 +1121,15 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) { // while we're waiting for the PLLs to spin up, determine which // band we're in and choose the base RSSI. +#if defined(CFG_sx1276_radio) if (LMIC.freq > SX127X_FREQ_LF_MAX) { - rssiAdjust = SX127X_RSSI_ADJUST_HF; + rssiAdjust = SX1276_RSSI_ADJUST_HF; } else { - rssiAdjust = SX127X_RSSI_ADJUST_LF; + rssiAdjust = SX1276_RSSI_ADJUST_LF; } +#elif defined(CFG_sx1272_radio) + rssiAdjust = SX1272_RSSI_ADJUST; +#endif rssiAdjust += hal_getRssiCal(); // zero the results @@ -903,7 +1164,7 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) { rssiMin = rssiNow; rssiSum += rssiNow; ++rssiN; - // TODO(tmm@mcci.com) move this to os_getTime(). + // TODO(tmm@mcci.com) move this to os_getTime(). hal_enableIRQs(); now = os_getTime(); hal_disableIRQs(); @@ -958,6 +1219,8 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { #endif if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem u1_t flags = readReg(LORARegIrqFlags); + LMIC.saveIrqFlags = flags; + LMICOS_logEventUint32("radio_irq_handler_v2: LoRa", flags); LMIC_X_DEBUG_PRINTF("IRQ=%02x\n", flags); if( flags & IRQ_LORA_TXDONE_MASK ) { // save exact tx time @@ -969,7 +1232,7 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { } LMIC.rxtime = now; // read the PDU and inform the MAC that we received something - LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ? + LMIC.dataLen = (readReg(LORARegModemConfig1) & SX127X_MC1_IMPLICIT_HEADER_MODE_ON) ? readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes); // set FIFO read address pointer writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); @@ -984,8 +1247,8 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { // indicate timeout LMIC.dataLen = 0; #if LMIC_DEBUG_LEVEL > 0 - ostime_t now2 = os_getTime(); - LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry, + ostime_t now2 = os_getTime(); + LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry, LMIC.rxtime, entry - LMIC.rxtime, now2 - entry, LMIC.rxtime-LMIC.txend); #endif } @@ -996,6 +1259,9 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { } else { // FSK modem u1_t flags1 = readReg(FSKRegIrqFlags1); u1_t flags2 = readReg(FSKRegIrqFlags2); + + LMICOS_logEventUint32("radio_irq_handler_v2: FSK", (flags2 << UINT32_C(8)) | flags1); + if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) { // save exact tx time LMIC.txend = now; @@ -1013,10 +1279,15 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) { // indicate timeout LMIC.dataLen = 0; } else { - ASSERT(0); + // ASSERT(0); + // we're not sure why we're here... treat as timeout. + LMIC.dataLen = 0; } + + // in FSK, we need to put the radio in standby first. + opmode(OPMODE_STANDBY); } - // go from stanby to sleep + // go from standby to sleep opmode(OPMODE_SLEEP); // run os job (use preset func ptr) os_setCallback(&LMIC.osjob, LMIC.osjob.func);