/* * 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 CFG_LMIC_US_like #ifndef LMICuslike_getFirst500kHzDR # error "LMICuslike_getFirst500kHzDR() not defined by bandplan" #endif static void setNextChannel(uint start, uint end, uint count) { ASSERT(count>0); ASSERT(start>= 1) { if (chmap & 1) { LMIC_enableSubBand(subband); } else { LMIC_disableSubBand(subband); } } } else { u1_t base, top; 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; // enable or disable all 125kHz channels for (u1_t chnl = 0; chnl < 64; ++chnl) { if (en125) 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); } } 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 // but also do the channel hopping dance. ostime_t LMICuslike_nextTx(ostime_t now) { // TODO(tmm@mcci.com): use a static const for US-like if (LMIC.datarate >= LMICuslike_getFirst500kHzDR()) { // 500kHz 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 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 LMIC.txChnl = 0; // then chose a new channel. This gives us a random first channel for // the join. Minor nit: if channel 0 is enabled, it will never be used // as the first join channel. The join logic uses the current txChnl, // then changes after the rx window expires; so we need to set a valid // starting point. setNextChannel(0, 64, LMIC.activeChannels125khz); // make sure LMIC.txend is valid. LMIC.txend = os_getTime(); ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0); // 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. } #endif // !DISABLE_JOIN #if !defined(DISABLE_JOIN) // // TODO(tmm@mcci.com): // // The definition of this is a little strange. this seems to return a time, but // in reality it returns 0 if the caller should continue scanning through // channels, and 1 if the caller has scanned all channels on this session, // and therefore should reset to the beginning. The IBM 1.6 code is the // same way, so apparently I just carried this across. We should declare // as bool_t and change callers to use the result clearly as a flag. // ostime_t LMICuslike_nextJoinState(void) { // Try the following: // DR0 (SF10) on a random channel 0..63 // (honoring enable mask) // DR4 (SF8C) on a random 500 kHz channel 64..71 // (always determined by // previously selected // 125 kHz channel) // u1_t failed = 0; // TODO(tmm@mcci.com) parameterize for US-like if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) { // assume that 500 kHz equiv of last 125 kHz channel // is also enabled, and use it next. LMIC.txChnl = 64 + (LMIC.txChnl >> 3); LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR()); } else { setNextChannel(0, 64, LMIC.activeChannels125khz); // TODO(tmm@mcci.com) parameterize s1_t dr = LMICuslike_getJoin125kHzDR(); if ((++LMIC.txCnt & 0x7) == 0) { failed = 1; // All DR exhausted - signal failed } LMICcore_setDrJoin(DRCHG_SET, dr); } LMIC.opmode &= ~OP_NEXTCHNL; // TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized; // starting adding a bias after 1 hour, 25 hours, etc.; and limit the duty // cycle on power up. For testability, add a way to set the join start time // externally (a test API) so we can check this feature. // See https://github.com/mcci-catena/arduino-lmic/issues/2 // Current code doesn't match LoRaWAN 1.0.2 requirements. LMIC.txend = os_getTime() + (isTESTMODE() // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it - GW is still busy) ? DNW2_SAFETY_ZONE // Otherwise: randomize join (street lamp case): // SF10:16, SF9=8,..SF8C:1secs : LMICcore_rndDelay(16 >> LMIC.datarate)); // 1 - triggers EV_JOIN_FAILED event return failed; } #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