From 27b63d955a6548722648c4a3b9b5be49bd3db04d Mon Sep 17 00:00:00 2001 From: Manuel Bleichenbacher <10954524+manuelbl@users.noreply.github.com> Date: Tue, 23 Apr 2019 21:03:41 +0200 Subject: [PATCH] Latest LMIC code from https://github.com/mcci-catena/arduino-lmic --- src/lmic/config.h | 30 +- src/lmic/hal.h | 20 +- src/lmic/lmic.c | 633 ++++++++++++++++++++++++--------- src/lmic/lmic.h | 227 +++++++++--- src/lmic/lmic_as923.c | 4 +- src/lmic/lmic_au921.c | 46 ++- src/lmic/lmic_bandplan.h | 15 +- src/lmic/lmic_bandplan_as923.h | 8 +- src/lmic/lmic_bandplan_au921.h | 17 +- src/lmic/lmic_bandplan_eu868.h | 5 +- src/lmic/lmic_bandplan_in866.h | 11 +- src/lmic/lmic_bandplan_us915.h | 16 +- src/lmic/lmic_env.h | 34 ++ src/lmic/lmic_eu868.c | 26 +- src/lmic/lmic_eu_like.c | 45 ++- src/lmic/lmic_eu_like.h | 16 +- src/lmic/lmic_in866.c | 25 +- src/lmic/lmic_us915.c | 64 +++- src/lmic/lmic_us_like.c | 118 +++--- src/lmic/lmic_us_like.h | 23 +- src/lmic/lorabase.h | 10 +- src/lmic/oslmic.c | 42 ++- src/lmic/oslmic.h | 22 +- src/lmic/radio.c | 24 +- 24 files changed, 1113 insertions(+), 368 deletions(-) diff --git a/src/lmic/config.h b/src/lmic/config.h index 253fd0a..32e30a9 100755 --- a/src/lmic/config.h +++ b/src/lmic/config.h @@ -123,15 +123,7 @@ //#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 -// 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 +170,24 @@ # 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 + #endif // _lmic_config_h_ diff --git a/src/lmic/hal.h b/src/lmic/hal.h index 5b610f4..8fadeb6 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 */ diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index a7f253c..d825bdd 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 @@ -40,7 +40,12 @@ 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); @@ -406,7 +411,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 +423,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,25 +461,131 @@ 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)<= LENOF_TABLE(macCmdSize) - 2) + return 0; + return TABLE_GET_U1(macCmdSize, macCmd - 2); +} + +static void +applyAdrRequests( + const uint8_t *opts, + int olen +) { + if( (LMIC.ladrAns & 0x7F) == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) { + lmic_saved_adr_state_t initialState; + int oidx; + u1_t p1 = 0; + u1_t p4 = 0; + + LMICbandplan_saveAdrState(&initialState); + + for (oidx = 0; oidx < olen; ) { + u1_t const cmd = opts[oidx]; + + if (cmd == MCMD_LADR_REQ) { + 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]; + u1_t chpage = p4 & MCMD_LADR_CHPAGE_MASK; // channel page + + LMICbandplan_mapChannels(chpage, chmap); + } + + int cmdlen = getMacCmdSize(cmd); + + // this really is an assert, we should never here + // unless all the commands are valid. + ASSERT(cmdlen != 0); + + oidx += cmdlen; + } + + // all done scanning options + bit_t changes = LMICbandplan_compareAdrState(&initialState); + + // handle uplink repeat count + u1_t uprpt = p4 & MCMD_LADR_REPEAT_MASK; // up repeat count + if (LMIC.upRepeat != uprpt) { + LMIC.upRepeat = uprpt; + changes = 1; + } + + // handle power changes + dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); + changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); + + LMIC.adrChanged = changes; // move the ADR FSM up to "time to request" + } +} + // scan mac commands starting at opts[] for olen, return count of bytes consumed. static int scan_mac_cmds( @@ -545,28 +740,37 @@ scan_mac_cmds( int olen ) { int oidx = 0; + // this parser is *really* fragile, especially for LinkADR requests. + // it won't crash, but acks will be wrong if all ADR requests are + // not contiguous. + bit_t fSawAdrReq = 0; + uint8_t cmd; + while( oidx < olen ) { - switch( opts[oidx] ) { + cmd = opts[oidx]; + switch( cmd ) { case MCMD_LCHK_ANS: { //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) ) + // TODO(tmm@mcci.com): LoRaWAN 1.1 & 1.0.3 requires us to send one ack + // for each LinkADRReq in a given MAC message. This code only sends + // ack for all the LinkADRReqs. Fixing this is a lot of work, and TTN + // behaves correctly with the current LMIC, so we'll leave this for + // the fix of issue #87. + if (! fSawAdrReq) { + fSawAdrReq = 1; + LMIC.ladrAns = 0x80 | // Include an answer into next frame up + MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; + } + if( !LMICbandplan_canMapChannels(chpage, chmap) ) LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK; dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); if( !validDR(dr) ) { @@ -576,23 +780,7 @@ scan_mac_cmds( 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; + break; } case MCMD_DEVS_REQ: { LMIC.devsAns = 1; @@ -600,8 +788,7 @@ scan_mac_cmds( 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; + break; } case MCMD_DN2P_SET: { #if !defined(DISABLE_MCMD_DN2P_SET) @@ -624,8 +811,7 @@ scan_mac_cmds( DO_DEVDB(LMIC.dn2Freq,dn2Freq); } #endif // !DISABLE_MCMD_DN2P_SET - oidx += 5; - continue; + break; } case MCMD_DCAP_REQ: { #if !defined(DISABLE_MCMD_DCAP_REQ) @@ -637,9 +823,8 @@ scan_mac_cmds( LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); LMIC.dutyCapAns = 1; - oidx += 2; #endif // !DISABLE_MCMD_DCAP_REQ - continue; + break; } case MCMD_SNCH_REQ: { #if !defined(DISABLE_MCMD_SNCH_REQ) @@ -650,8 +835,7 @@ scan_mac_cmds( 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; + break; } case MCMD_PING_SET: { #if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) @@ -666,8 +850,7 @@ scan_mac_cmds( } LMIC.pingSetAns = flags; #endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING - oidx += 4; - continue; + break; } case MCMD_BCNI_ANS: { #if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS) @@ -695,8 +878,7 @@ scan_mac_cmds( 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: { #if LMIC_ENABLE_TxParamSetupReq @@ -710,8 +892,7 @@ scan_mac_cmds( LMIC.txParam = txParam; LMIC.txParamSetupAns = 1; #endif // LMIC_ENABLE_TxParamSetupReq - oidx += 2; - continue; + break; } /* end case */ case MCMD_DeviceTimeAns: { #if LMIC_ENABLE_DeviceTimeReq @@ -742,21 +923,41 @@ scan_mac_cmds( #endif } #endif // LMIC_ENABLE_DeviceTimeReq - oidx += 6; - continue; + break; + } /* end case */ + + 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; + + /* compute length, and exit for illegal commands */ + int const cmdlen = getMacCmdSize(cmd); + if (cmdlen == 0) { + // "the first unknown command terminates processing" + // force olen to current oidx so we'll exit the while(). + olen = oidx; + } + + oidx += cmdlen; } /* end while */ + + // go back and apply the ADR changes, if any -- use the effective length + if (fSawAdrReq) + applyAdrRequests(opts, olen); + return oidx; } +// change the ADR ack request count, unless adr ack is diabled. +static void setAdrAckCount (s1_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]; @@ -858,8 +1059,7 @@ 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); 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. @@ -877,6 +1077,7 @@ static bit_t decodeFrame (void) { 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 ) { @@ -956,13 +1157,19 @@ 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(); } @@ -973,11 +1180,11 @@ static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { // If a clock error is specified, compensate for it by extending the // receive window - if (LMIC.clockError != 0) { + 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.clockError / MAX_CLOCK_ERROR; + 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). @@ -1006,7 +1213,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(); } @@ -1033,7 +1240,6 @@ static void txDone (ostime_t delay, osjobcb_t func) { } } - // ======================================== Join frames @@ -1043,53 +1249,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 +1274,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); @@ -1159,15 +1328,19 @@ static bit_t processJoinAccept (void) { // // 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. + // 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 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 +1348,74 @@ 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. +#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 + // stop this join process. + return 1; +} static void processRx2Jacc (xref2osjob_t osjob) { LMIC_API_PARAMETER(osjob); @@ -1190,7 +1423,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(); } @@ -1326,8 +1561,11 @@ static void buildDataFrame (void) { } #endif // !DISABLE_BEACONS if( LMIC.adrChanged ) { - if( LMIC.adrAckReq < 0 ) - LMIC.adrAckReq = 0; + // 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; } #if !defined(DISABLE_MCMD_DN2P_SET) @@ -1378,7 +1616,7 @@ static void buildDataFrame (void) { } 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) + | (LMIC.adrAckReq >= LINK_CHECK_CONT ? FCT_ADRARQ : 0) | (end-OFF_DAT_OPTS)); os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); @@ -1438,7 +1676,7 @@ 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 ) { @@ -1454,7 +1692,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 } @@ -1554,7 +1792,28 @@ 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); + + // put us in unjoined state: + LMIC.devaddr = 0; + + // clear transmit. + LMIC_clrTxData(); } // Start join procedure if not already joined. @@ -1568,9 +1827,10 @@ bit_t LMIC_startJoining (void) { LMIC.opmode &= ~(OP_SCAN|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; } @@ -1592,8 +1852,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 @@ -1622,8 +1881,7 @@ static bit_t processDnData (void) { // Nothing received - implies no port initTxrxFlags(__func__, TXRX_NOPORT); } - if( LMIC.adrAckReq != LINK_CHECK_OFF ) - LMIC.adrAckReq += 1; + setAdrAckCount(LMIC.adrAckReq + 1); LMIC.dataBeg = LMIC.dataLen = 0; txcomplete: LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); @@ -1631,25 +1889,25 @@ static bit_t processDnData (void) { #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; + 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.pNetworkTimeCb = NULL; + LMIC.client.pNetworkTimeCb = NULL; // call the user's notification routine. - (*pNetworkTimeCb)(LMIC.pNetworkTimeUserData, flagSuccess); + (*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; - reportEvent(EV_LINK_ALIVE); + reportEventNoUpdate(EV_LINK_ALIVE); } - reportEvent(EV_TXCOMPLETE); + 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 ) { @@ -1658,16 +1916,38 @@ static bit_t processDnData (void) { 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); + dr_t newDr = decDR((dr_t)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. + // REJOIN sends a single join packet then brings back + // up the normal tx loop. If we miss the downlink for the + // join-accept, all the TXs until the next window will + // fail... so we leave in the OP_REJOIN, but set things + // to trigger a REJOIN after each uplink from here on. + LMIC.adrAckReq = LINK_CHECK_DEAD; +#if !defined(DISABLE_JOIN) + LMIC.opmode |= OP_REJOIN; +#endif // !defined(DISABLE_JOIN) + } else { + // not in the dead state... let's wait another 32 + // uplinks before panicking. + 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_BCNI_REQ commands - try full scan if( LMIC.bcninfoTries > 0 ) { if( (LMIC.opmode & OP_TRACK) != 0 ) { - reportEvent(EV_BEACON_FOUND); + reportEventNoUpdate(EV_BEACON_FOUND); // update? LMIC.bcninfoTries = 0; } else if( --LMIC.bcninfoTries == 0 ) { @@ -1734,7 +2014,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,25 +2026,26 @@ 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 @@ -1837,10 +2118,8 @@ static void engineUpdate (void) { // in AS923 v1.1 or older, no need to change the datarate. 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 @@ -1874,7 +2153,9 @@ static void engineUpdate (void) { 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 +2201,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)); @@ -1954,7 +2236,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,8 +2247,16 @@ 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; @@ -2001,8 +2294,13 @@ void LMIC_init (void) { void LMIC_clrTxData (void) { + bit_t const txActive = LMIC.opmode & OP_TXDATA; LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL); LMIC.pendTxLen = 0; + + if (txActive) + reportEventNoUpdate(EV_TXCANCELED); + if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING return; os_clearCallback(&LMIC.osjob); @@ -2019,7 +2317,7 @@ void LMIC_setTxData (void) { } -// +// send a message w/o callback int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) return -2; @@ -2032,6 +2330,20 @@ int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { return 0; } +// send a message with callback +int LMIC_sendWithCallback( + u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, + lmic_txmessage_cb_t *pCb, void *pUserData +) { + int const result = LMIC_setTxData2(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; @@ -2075,6 +2387,9 @@ void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t LMIC.opmode &= ~(OP_JOINING|OP_TRACK|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 +2415,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 +2450,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..faa8a12 100755 --- a/src/lmic/lmic.h +++ b/src/lmic/lmic.h @@ -105,7 +105,7 @@ extern "C"{ #define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \ (((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local)) -#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 3, 2, 0) /* v2.3.2 */ +#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 3, 2, 50) /* v2.3.2.50 */ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ (((v) >> 24u) & 0xFFu) @@ -122,14 +122,17 @@ 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_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,12 +156,23 @@ 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 +}; + #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 { KEEP_TXPOW = -128 }; @@ -189,14 +203,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 @@ -235,7 +249,7 @@ enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, 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; enum { @@ -243,6 +257,12 @@ enum { 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,26 +292,108 @@ enum lmic_request_time_state_e { typedef u1_t lmic_request_time_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]; @@ -304,45 +406,46 @@ 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 + +#if !defined(DISABLE_BEACONS) + s2_t drift; // last measured drift + s2_t lastDriftDiff; + s2_t maxDriftDiff; +#endif + + /* (u)int8_t things */ + 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, limited by adrTxPow. + 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 + 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 pendTxData[MAX_LEN_PAYLOAD]; - u2_t devNonce; // last generated nonce 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) @@ -376,7 +479,6 @@ struct lmic_t { // 2nd RX window (after up stream) u1_t dn2Dr; - u4_t dn2Freq; #if !defined(DISABLE_MCMD_DN2P_SET) u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs #endif @@ -389,10 +491,6 @@ struct lmic_t { #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 +501,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 +515,19 @@ 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); #endif void LMIC_shutdown (void); @@ -436,6 +536,7 @@ 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); +int LMIC_sendWithCallback(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 +548,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 +560,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..5050ff7 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 @@ -138,7 +138,7 @@ int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1) { (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT ); - return adj; + return LMICas923_getMaxEIRP(LMIC.txParam) + adj; } // only used in this module, but used by variant macro dr2hsym(). diff --git a/src/lmic/lmic_au921.c b/src/lmic/lmic_au921.c index bd0b629..a6d0fdb 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 @@ -109,9 +109,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 +121,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,42 +136,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 LMICau921_updateTx(ostime_t txbeg) { @@ -213,6 +228,13 @@ void LMICau921_setRx1Params(void) { LMIC.rps = dndr2rps(LMIC.dndr); } +void LMICau921_initJoinLoop(void) { + LMICuslike_initJoinLoop(); + + // initialize the adrTxPower. + LMIC.adrTxPow = 30; // dBm + +} // // END: AU921 related stuff diff --git a/src/lmic/lmic_bandplan.h b/src/lmic/lmic_bandplan.h index 0c3c503..5c837a4 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 @@ -104,6 +104,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,6 +147,15 @@ #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 + // // Things common to lmic.c code // diff --git a/src/lmic/lmic_bandplan_as923.h b/src/lmic/lmic_bandplan_as923.h index 50017f2..9695605 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,8 +26,8 @@ * 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" @@ -112,4 +112,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..560a1f5 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,12 +26,12 @@ * 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" @@ -46,7 +46,10 @@ 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 +63,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..63c419a 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 @@ -89,4 +89,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..8c0cb95 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,8 +26,8 @@ * 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" @@ -82,4 +82,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_us915.h b/src/lmic/lmic_bandplan_us915.h index 28ae2c8..f687095 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,11 +26,12 @@ * 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" @@ -45,7 +46,7 @@ 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 +54,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_env.h b/src/lmic/lmic_env.h index 4610fb3..032f44b 100755 --- a/src/lmic/lmic_env.h +++ b/src/lmic/lmic_env.h @@ -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..e47082b 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 @@ -59,7 +59,7 @@ uint8_t LMICeu868_maxFrameLen(uint8_t dr) { } 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, 0,0,0,0, 0,0,0,0 }; int8_t LMICeu868_pow2dBm(uint8_t mcmd_ladr_p1) { @@ -221,6 +221,28 @@ LMICeu868_txDoneFSK(ostime_t delay, osjobcb_t func) { 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; + + 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 LMICeu868_initJoinLoop(void) { LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ EU868_TX_EIRP_MAX_DBM); diff --git a/src/lmic/lmic_eu_like.c b/src/lmic/lmic_eu_like.c index 60c3006..2f51069 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,37 +32,47 @@ #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 +bit_t LMICeulike_canMapChannels(u1_t chpage, u2_t chmap) { 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; +} + #endif // CFG_LMIC_EU_like diff --git a/src/lmic/lmic_eu_like.h b/src/lmic/lmic_eu_like.h index f147790..0f51c85 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 @@ -74,14 +74,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 +95,10 @@ 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) + #endif // _lmic_eu_like_h_ diff --git a/src/lmic/lmic_in866.c b/src/lmic/lmic_in866.c index ad52134..1788ca4 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 @@ -59,7 +59,7 @@ uint8_t LMICin866_maxFrameLen(uint8_t dr) { } 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, 0, 0,0,0,0 }; int8_t LMICin866_pow2dBm(uint8_t mcmd_ladr_p1) { @@ -195,6 +195,27 @@ LMICin866_txDoneFSK(ostime_t delay, osjobcb_t func) { 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; + + 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 LMICin866_initJoinLoop(void) { LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ IN866_TX_EIRP_MAX_DBM); diff --git a/src/lmic/lmic_us915.c b/src/lmic/lmic_us915.c index 46f16b6..1d524d4 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 @@ -98,9 +98,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 +110,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 +125,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 +208,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..9ec4e61 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,7 +91,46 @@ 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 + || 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_LADR_CHP_USLIKE_SPECIAL) { + if (chmap == 0) + return 0; + + // operate on channels 0..15, 16..31, 32..47, 48..63, 64..71 + if (chpage == (64 >> 4)) { + if (chmap & 0xFF00) { + // those are reserved bits, fail. + return 0; + } + } + } else if (chpage == MCMD_LADR_CHP_BANK) { + if (chmap == 0 || (chmap & 0xFF00) != 0) { + // no bits set, or reserved bitsset , fail. + return 0; + } + } else if (chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF) { + u1_t const en125 = chpage == MCMD_LADR_CHP_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; +} + +bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { /* || MCMD_LADR_CHP_125ON and MCMD_LADR_CHP_125OFF are special. The || channel map appllies to 500kHz (ch 64..71) and in addition @@ -99,61 +138,53 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) { || is also special, in that it enables subbands. */ u1_t base, top; + bit_t result = 0; + + if (chpage == MCMD_LADR_CHP_BANK) { + // each bit enables a bank of channels + for (u1_t subband = 0; subband < 8; ++subband, chmap >>= 1) { + if (chmap & 1) { + result |= LMIC_enableSubBand(subband); + } else { + result |= LMIC_disableSubBand(subband); + } + } + + return result; + } 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 (chmap & 0xFF00) { - // those are reserved bits, fail. - return 0; - } top = 72; } - } else if (chpage == MCMD_LADR_CHP_BANK) { - if (chmap & 0xFF00) { - // those are resreved bits, fail. - return 0; - } - // each bit enables a bank of channels - for (u1_t subband = 0; subband < 8; ++subband, chmap >>= 1) { - if (chmap & 1) { - LMIC_enableSubBand(subband); - } 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) { + } else /* if (chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF) */ { u1_t const en125 = chpage == MCMD_LADR_CHP_125ON; // enable or disable all 125kHz channels for (u1_t chnl = 0; chnl < 64; ++chnl) { if (en125) - LMIC_enableChannel(chnl); + result |= LMIC_enableChannel(chnl); else - LMIC_disableChannel(chnl); + result |= LMIC_disableChannel(chnl); } // then apply mask to top 8 channels. base = 64; top = 72; - } else { - return 0; } // 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); + result |= LMIC_enableChannel(chnl); else - LMIC_disableChannel(chnl); + result |= LMIC_disableChannel(chnl); } - return 1; + return result; } // US does not have duty cycling - return now as earliest TX time @@ -183,18 +214,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 +259,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 +286,16 @@ ostime_t LMICuslike_nextJoinState(void) { } #endif +void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) { + memcpy( + pStateBuffer->channelMap, + LMIC.channelMap, + sizeof(LMIC.channelMap) + ); +} + +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..ba6dc89 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) { @@ -77,24 +80,30 @@ 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) + #endif // _lmic_us_like_h_ diff --git a/src/lmic/lorabase.h b/src/lmic/lorabase.h index 656ea2c..ff2f10b 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 }; @@ -414,7 +421,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 { 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..8126704 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[]; @@ -149,7 +148,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 +162,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 +200,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. diff --git a/src/lmic/radio.c b/src/lmic/radio.c index 5320a1c..4d33cf0 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 @@ -188,6 +188,12 @@ #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 + // sx1276 RegModemConfig2 #define SX1276_MC2_RX_PAYLOAD_CRCON 0x04 @@ -264,7 +270,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---- @@ -445,7 +451,7 @@ static void configChannel () { static void configPower () { #ifdef CFG_sx1276_radio // PA_BOOST output is assumed but not 20 dBm. - s1_t pw = (s1_t)LMIC.txpow; + s1_t pw = (s1_t)LMIC.radio_txpow; if(pw > 17) { pw = 17; } else if(pw < 2) { @@ -461,7 +467,7 @@ static void configPower () { #elif CFG_sx1272_radio // set PA config (2-17 dBm using PA_BOOST) - s1_t pw = (s1_t)LMIC.txpow; + s1_t pw = (s1_t)LMIC.radio_txpow; if(pw > 17) { pw = 17; } else if(pw < 2) { @@ -634,8 +640,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 @@ -660,6 +666,9 @@ 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 @@ -958,6 +967,7 @@ 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; LMIC_X_DEBUG_PRINTF("IRQ=%02x\n", flags); if( flags & IRQ_LORA_TXDONE_MASK ) { // save exact tx time @@ -969,7 +979,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));