diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 4080d2ff..7f3e2385 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1436,27 +1436,18 @@ uint32_t SX126x::getTimeOnAir(size_t len) { } } -uint32_t SX126x::calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUs) { - uint32_t symbolTime = (0x00000001 << datarate->lora.spreadingFactor) * 1000 / datarate->lora.bandwidth; - timeoutUs = (uint32_t)numSymbols * symbolTime + offsetUs; +uint32_t SX126x::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) { + (void)numSymbols; // not used for these modules uint32_t timeout = timeoutUs / 15.625; return(timeout); } -bool SX126x::isRxTimeout() { - uint16_t irq = getIrqStatus(); - bool isRxTimeout = false; - if(irq & RADIOLIB_SX126X_IRQ_TIMEOUT) { - isRxTimeout = true; - } - return(isRxTimeout); -} - -uint16_t SX126x::readIrq(bool clear) { - uint16_t irq = getIrqStatus(); - if(clear) - clearIrqStatus(); - return(irq); +int16_t SX126x::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { + irqFlags = 0b0000000000000010; // RxDone + irqMask = 0b0000000000000010; + irqFlags |= 0b0000001000000000; // RxTimeout + irqMask |= 0b0000001000000000; + return(RADIOLIB_ERR_NONE); } int16_t SX126x::implicitHeader(size_t len) { diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index 3e4c69df..ffef9fbd 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -950,26 +950,20 @@ class SX126x: public PhysicalLayer { uint32_t getTimeOnAir(size_t len) override; /*! - \brief Get the Rx timeout required to listen to a preamble of a certain number of symbols - \param numSymbols Number of symbols to listen for - \param datarate The datarate for which to calculate the timeout - \param offsetUs Additional offset in microseconds to allow increasing the timeout - \param timeoutUs Returns the timeout in microseconds for the host to sleep + \brief Calculate the timeout value for this specific module / series based on number of symbols or time + \param numSymbols Number of payload symbols to listen for + \param timeoutUs Timeout in microseconds to listen for \returns Timeout value in a unit that is specific for the used module */ - uint32_t calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUss); + uint32_t calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs); /*! - \brief Check whether there is a RxTimeout flag set - \returns RxTimeout flag is set + \brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks + \param irqFlags The flags for which IRQs must be triggered + \param irqMask Mask indicating which IRQ triggers a DIO + \returns \ref status_codes */ - bool isRxTimeout(); - - /*! - \brief Check whether there is a RxTimeout flag set - \returns RxTimeout flag is set - */ - uint16_t readIrq(bool clear = false); + int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); /*! \brief Set implicit header mode for future reception/transmission. diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 3b522153..65359b33 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -4,30 +4,19 @@ #if !defined(RADIOLIB_EXCLUDE_LORAWAN) -// flag to indicate whether we have received a downlink -static volatile bool downlinkReceived = false; - #if defined(RADIOLIB_EEPROM_UNSUPPORTED) #warning "Persistent storage not supported!" #endif -// interrupt service routine to handle downlinks automatically -#if defined(ESP8266) || defined(ESP32) - IRAM_ATTR -#endif -static void LoRaWANNodeOnDownlink(void) { - downlinkReceived = true; -} - -// flag to indicate whether channel scan operation is complete -static volatile bool downlinkTimeout = false; +// flag to indicate whether there was some action during Rx mode (timeout or downlink) +static volatile bool downlinkAction = false; // interrupt service routine to handle downlinks automatically #if defined(ESP8266) || defined(ESP32) IRAM_ATTR #endif -static void LoRaWANNodeOnChannelScan(void) { - downlinkTimeout = true; +static void LoRaWANNodeOnDownlinkAction(void) { + downlinkAction = true; } LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { @@ -48,10 +37,6 @@ void LoRaWANNode::wipe() { int16_t LoRaWANNode::restore() { // check the magic value Module* mod = this->phyLayer->getMod(); - if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) { - // the magic value is not set, user will have to do perform the join procedure - return(RADIOLIB_ERR_NETWORK_NOT_JOINED); - } uint16_t nvm_table_version = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION_ID); // if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_TABLE_VERSION > nvm_table_version) { @@ -59,6 +44,12 @@ int16_t LoRaWANNode::restore() { // } (void)nvm_table_version; + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) { + RADIOLIB_DEBUG_PRINTLN("magic id: %d", mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID)); + // the magic value is not set, user will have to do perform the join procedure + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + // pull all authentication keys from persistent storage this->devAddr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); @@ -67,10 +58,10 @@ int16_t LoRaWANNode::restore() { mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); // get session parameters - this->rev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); + this->rev = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION); RADIOLIB_DEBUG_PRINTLN("LoRaWAN session: v1.%d", this->rev); - this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); - this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); + this->devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); + this->joinNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID); // get MAC state uint8_t txDrRx2Dr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID); @@ -79,25 +70,25 @@ int16_t LoRaWANNode::restore() { this->txPwrCur = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID); uint8_t rx1DrOffDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID); - this->rx1DrOffset = (rx1DrOffDel >> 4) & 0x0F; + this->rx1DrOffset = (rx1DrOffDel >> 4) & 0x0F; this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] + this->band->rx1DataRateBase + this->rx1DrOffset; - this->rxDelays[0] = ((rx1DrOffDel >> 0) & 0x0F) * 1000; + this->rxDelays[0] = ((rx1DrOffDel >> 0) & 0x0F) * 1000; if(this->rxDelays[0] == 0) { this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS; } - this->rxDelays[1] = this->rxDelays[0] + 1000; + this->rxDelays[1] = this->rxDelays[0] + 1000; uint8_t rx2FreqBuf[3]; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); - uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); - this->rx2.drMax = (txDrRx2Dr >> 0) & 0x0F; - this->rx2.freq = (float)rx2Freq / 10000.0; + uint32_t rx2Freq = LoRaWANNode::ntoh(&rx2FreqBuf[0], 3); + this->rx2.drMax = (txDrRx2Dr >> 0) & 0x0F; + this->rx2.freq = (float)rx2Freq / 10000.0; - uint8_t adrLimDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); - this->adrLimitExp = (adrLimDel >> 4) & 0x0F; - this->adrDelayExp = (adrLimDel >> 0) & 0x0F; + uint8_t adrLimDel = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID); + this->adrLimitExp = (adrLimDel >> 4) & 0x0F; + this->adrDelayExp = (adrLimDel >> 0) & 0x0F; - this->nbTrans = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID); + this->nbTrans = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID); this->aFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID); this->nFcntDown = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID); @@ -127,7 +118,6 @@ int16_t LoRaWANNode::restoreFcntUp() { uint8_t fcntBuff[30] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, 30); - RADIOLIB_DEBUG_HEXDUMP(fcntBuff, 30); // copy the two most significant bytes from the first two bytes uint8_t bits_30_22 = fcntBuff[0]; @@ -226,46 +216,16 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // send it state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); - RADIOLIB_ASSERT(state); - - // configure for downlink with default configuration - state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); - RADIOLIB_ASSERT(state); - - // set the function that will be called when the reply is received - this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); - - // downlink messages are sent with inverted IQ - // TODO use downlink() for this - if(!this->FSK) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } - - // start receiving - uint32_t start = mod->hal->millis(); - downlinkReceived = false; - state = this->phyLayer->startReceive(); + this->rxDelayStart = mod->hal->millis(); RADIOLIB_ASSERT(state); - // wait for the reply or timeout - while(!downlinkReceived) { - if(mod->hal->millis() - start >= RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS + 2000) { - downlinkReceived = false; - if(!this->FSK) { - this->phyLayer->invertIQ(false); - } - return(RADIOLIB_ERR_RX_TIMEOUT); - } - } + // configure Rx delay for join-accept message - these are re-configured once a valid join-request is received + this->rxDelays[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; + this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; - // we have a message, reset the IQ inversion - downlinkReceived = false; - this->phyLayer->clearPacketReceivedAction(); - if(!this->FSK) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); - } + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + state = downlinkCommon(); + RADIOLIB_ASSERT(state); // build the buffer for the reply data uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; @@ -508,22 +468,22 @@ int16_t LoRaWANNode::saveSession() { Module* mod = this->phyLayer->getMod(); // store session configuration (MAC commands) - if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION) != this->rev) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION, this->rev); + if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION) != this->rev) + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION, this->rev); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID) != this->devNonce) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, this->devNonce); uint8_t txDrRx2Dr = (this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] << 4) | this->rx2.drMax; if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID) != txDrRx2Dr) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXDR_RX2DR_ID, txDrRx2Dr); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID) != this->txPwrCur) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID, this->txPwrCur); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_TXPWR_CUR_ID, this->txPwrCur); uint8_t rx1DrOffDel = (this->rx1DrOffset << 4) | (this->rxDelays[0] / 1000); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID) != rx1DrOffDel) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX1_DROFF_DEL_ID, rx1DrOffDel); uint8_t rx2FreqBuf[3]; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX2FREQ_ID), rx2FreqBuf, 3); @@ -536,26 +496,26 @@ int16_t LoRaWANNode::saveSession() { uint8_t adrLimDel = (this->adrLimitExp << 4) | (this->adrDelayExp << 0); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID) != adrLimDel) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, adrLimDel); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_LIM_DEL_ID, adrLimDel); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID) != this->nbTrans) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID, this->nbTrans); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NBTRANS_ID, this->nbTrans); // store all frame counters if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID) != this->aFcntDown) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, this->aFcntDown); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, this->aFcntDown); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID) != this->nFcntDown) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, this->nFcntDown); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, this->nFcntDown); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID) != this->confFcntUp) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID, this->confFcntUp); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_UP_ID, this->confFcntUp); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID) != this->confFcntDown) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID, this->confFcntDown); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_DOWN_ID, this->confFcntDown); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID) != this->adrFcnt) - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, this->adrFcnt); + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_FCNT_ID, this->adrFcnt); // fcntUp is saved using highly efficient wear-leveling as this is by far going to be written most often this->saveFcntUp(); @@ -578,7 +538,6 @@ int16_t LoRaWANNode::saveFcntUp() { uint8_t fcntBuff[30] = { 0 }; mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID), fcntBuff, 30); - RADIOLIB_DEBUG_HEXDUMP(fcntBuff, 30); // we discard the first two bits - your flash will likely be far dead by the time you reach 2^30 uplinks // the first two bytes of the remaining 30 bytes are stored straight into storage without additional wear leveling @@ -601,12 +560,16 @@ int16_t LoRaWANNode::saveFcntUp() { break; } } - idx = idx < 5 ? idx : 2; + // check if the last written byte is equal to current, only rewrite if different uint8_t bits_14_7 = (this->fcntUp >> 7) & 0x7F; + if((fcntBuff[idx - 1] & 0x7F) != bits_14_7) { + // find next index to write + idx = idx < 5 ? idx : 2; - // flip the first bit of this byte to indicate that we just wrote here - bits_14_7 |= (~(fcntBuff[idx] >> 7)) << 7; - mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_14_7, idx); + // flip the first bit of this byte to indicate that we just wrote here + bits_14_7 |= (~(fcntBuff[idx] >> 7)) << 7; + mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, bits_14_7, idx); + } // equally, the last 7 bits are stored into one of many indices // this index is indicated by the first byte that has its state (most significant bit) different from its predecessor @@ -663,20 +626,26 @@ int16_t LoRaWANNode::uplink(const char* str, uint8_t port, bool isConfirmed, boo } int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed, bool adrEnabled) { + Module* mod = this->phyLayer->getMod(); + + // check if sufficient time has elapsed since the last uplink + if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) { + // not enough time elapsed since the last uplink, we may still be in an RX window + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); + } + // check destination port if(port > 0xDF) { return(RADIOLIB_ERR_INVALID_PORT); } // port 0 is only allowed for MAC-only payloads if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { - if (!isMACPayload) { + if (!this->isMACPayload) { return(RADIOLIB_ERR_INVALID_PORT); } // if this is MAC only payload, continue and reset for next uplink - isMACPayload = false; + this->isMACPayload = false; } - - Module* mod = this->phyLayer->getMod(); // check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame) uint8_t foptsLen = 0; @@ -701,7 +670,8 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf bool adrAckReq = false; if((this->fcntUp - this->adrFcnt) >= adrLimit) { adrAckReq = true; - } else if ((this->fcntUp - this->adrFcnt) == (adrLimit + adrDelay)) { + } + if ((this->fcntUp - this->adrFcnt) == (adrLimit + adrDelay)) { // try one of three, in order: set TxPower to max, set DR to min, enable all defined channels // set the maximum power supported by both the module and the band @@ -740,12 +710,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK); RADIOLIB_ASSERT(state); - // check if sufficient time has elapsed since the last uplink - if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) { - // not enough time elapsed since the last uplink, we may still be in an RX window - return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); - } - // build the uplink message // the first 16 bytes are reserved for MIC calculation blocks size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsBufSize); @@ -774,7 +738,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf } } - // if the saved confirm-fcnt is equal to the last app-downlink, set the ACK bit and clear the confirm-fcnt + // if the saved confirm-fcnt is set, set the ACK bit if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; } @@ -825,9 +789,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf // create blocks for MIC calculation uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; - if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { - LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntDown); - } block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fcntUp); @@ -835,7 +796,9 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); - // TODO implement confirmed frames + if(this->confFcntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFcntDown); + } block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK]; block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->currentChannels[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK].idx; @@ -860,23 +823,123 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port, bool isConf RADIOLIB_DEBUG_HEXDUMP(uplinkMsg, uplinkMsgLen); // send it (without the MIC calculation blocks) - uint32_t txStart = mod->hal->millis(); - uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS); + + // set the timestamp so that we can measure when to start receiving + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_DEBUG_PRINTLN("Uplink sent <-- Rx Delay start"); + #if !defined(RADIOLIB_STATIC_ONLY) delete[] uplinkMsg; #endif RADIOLIB_ASSERT(state); - - // set the timestamp so that we can measure when to start receiving - this->rxDelayStart = txStart + timeOnAir; - - // the downlink confirmation was transmitted, so clear the counter value + + // the downlink confirmation was acknowledged, so clear the counter value this->confFcntDown = RADIOLIB_LORAWAN_FCNT_NONE; return(RADIOLIB_ERR_NONE); } +int16_t LoRaWANNode::downlinkCommon() { + // check if there are any upcoming Rx windows + Module* mod = this->phyLayer->getMod(); + const uint32_t scanGuard = 10; + if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) { + // time since last Tx is greater than RX2 delay + some guard period + // we have nothing to downlink + return(RADIOLIB_ERR_NO_RX_WINDOW); + } + + // configure for downlink + int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + RADIOLIB_ASSERT(state); + + // downlink messages are sent with inverted IQ + if(!this->FSK) { + state = this->phyLayer->invertIQ(true); + RADIOLIB_ASSERT(state); + } + + // calculate the Rx timeout + // according to the spec, this must be at least enough time to effectively detect a preamble + // but pad it a bit on both sides (start and end) to make sure it is wide enough + uint32_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000; + uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(0, timeoutHost); + + // create the masks that are required for receiving downlinks + uint16_t irqFlags; + uint16_t irqMask; + this->phyLayer->irqRxDoneRxTimeout(irqFlags, irqMask); + + downlinkAction = false; + this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); + + // perform listening in the two Rx windows + for(uint8_t i = 0; i < 2; i++) { + + // wait for the start of the Rx window + // the waiting duration is shortened a bit to cover any possible timing errors + uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); + if(waitLen > scanGuard) { + waitLen -= scanGuard; + } + mod->hal->delay(waitLen); + + // open Rx window by starting receive with specified timeout + state = this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); + RADIOLIB_DEBUG_PRINTLN("Opening Rx%d window (%d us timeout)... <-- Rx Delay end ", i+1, timeoutHost); + + // wait for the timeout to complete (and a small additional delay) + mod->hal->delay(timeoutHost / 1000 + scanGuard / 2); + RADIOLIB_DEBUG_PRINTLN("closing"); + + // check if the DIO has fired indicating a timeout; no timeout means a receive is ongoing + if(!downlinkAction) { + break; + + } else if(i == 0) { + // nothing in the first window, configure for the second + this->phyLayer->standby(); + state = this->phyLayer->setFrequency(this->rx2.freq); + RADIOLIB_ASSERT(state); + + DataRate_t dataRate; + findDataRate(this->rx2.drMax, &dataRate); + state = this->phyLayer->setDataRate(dataRate); + RADIOLIB_ASSERT(state); + timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*scanGuard*1000; + timeoutMod = this->phyLayer->calculateRxTimeout(0, timeoutHost); + downlinkAction = false; + } + + } + + // if we got here due to a timeout, stop ongoing activities + if(downlinkAction) { + this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? + if(!this->FSK) { + this->phyLayer->invertIQ(false); + } + + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // wait for the DIO to fire indicating a downlink is received + while(!downlinkAction) { + mod->hal->yield(); + } + + // we have a message, clear actions, go to standby and reset the IQ inversion + this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? + this->phyLayer->clearPacketReceivedAction(); + if(!this->FSK) { + state = this->phyLayer->invertIQ(false); + RADIOLIB_ASSERT(state); + } + + return(RADIOLIB_ERR_NONE); +} + #if defined(RADIOLIB_BUILD_ARDUINO) int16_t LoRaWANNode::downlink(String& str) { int16_t state = RADIOLIB_ERR_NONE; @@ -901,110 +964,11 @@ int16_t LoRaWANNode::downlink(String& str) { #endif int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { - // check if there are any upcoming Rx windows - Module* mod = this->phyLayer->getMod(); - const uint32_t scanGuard = 10; - if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) { - // time since last Tx is greater than RX2 delay + some guard period - // we have nothing to downlink - return(RADIOLIB_ERR_NO_RX_WINDOW); - } - - // configure for downlink - int16_t state = this->configureChannel(RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK); + + // handle Rx1 and Rx2 windows - returns RADIOLIB_ERR_NONE if a downlink is received + int16_t state = downlinkCommon(); RADIOLIB_ASSERT(state); - // downlink messages are sent with inverted IQ - if(!this->FSK) { - state = this->phyLayer->invertIQ(true); - RADIOLIB_ASSERT(state); - } - - downlinkTimeout = false; - downlinkReceived = false; - // this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan); - - // calculate the Rx timeout - // according to the spec, this must be at least enough time to effectively detect a preamble - // but pad it a bit on both sides (start and end) to make sure it is wide enough - DataRate_t dataRate; - findDataRate(this->dataRates[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK], &dataRate); - uint32_t timeoutHost = 0; - uint32_t timeoutMod = this->phyLayer->calculateRxTimeout(23, &dataRate, 2*scanGuard*1000, timeoutHost); - RADIOLIB_DEBUG_PRINTLN("RxTimeout host: %d, module: %06x", timeoutHost, timeoutMod); - - // perform listening in the two Rx windows - for(uint8_t i = 0; i < 2; i++) { - - // wait for the start of the Rx window - // the waiting duration is shortened a bit to cover any possible timing errors - uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); - if(waitLen > scanGuard) { - waitLen -= scanGuard; - } - mod->hal->delay(waitLen); - - // TODO somehow make this module-independent, currently configured for SX126x - uint16_t irqFlags = 0b0000000000000010; // RxDone - uint16_t irqMask = 0b0000000000000010; - irqFlags |= 0b0000001000000000; // RxTimeout - irqMask |= 0b0000001000000000; - - uint32_t rxStart = mod->hal->millis(); - this->phyLayer->startReceive(timeoutMod, irqFlags, irqMask, 0); - RADIOLIB_DEBUG_PRINT("Opened Rx%d window (%d us timeout)... ", i+1, timeoutHost); - mod->hal->delay(timeoutHost / 1000 + scanGuard); - RADIOLIB_DEBUG_PRINTLN("closing"); - if(this->phyLayer->isRxTimeout()) { - downlinkTimeout = true; - } - - // check if we detected something of a packet - if(!downlinkTimeout) { - break; - - } else if(i == 0) { - // nothing in the first window, configure for the second - this->phyLayer->standby(); - state = this->phyLayer->setFrequency(this->rx2.freq); - RADIOLIB_ASSERT(state); - - DataRate_t dataRate; - findDataRate(this->rx2.drMax, &dataRate); - state = this->phyLayer->setDataRate(dataRate); - RADIOLIB_ASSERT(state); - timeoutMod = this->phyLayer->calculateRxTimeout(22, &dataRate, 2*scanGuard*1000, timeoutHost); - downlinkTimeout = false; - } - - } - // this->phyLayer->clearChannelScanAction(); - - // check if we received a packet at all - if(downlinkTimeout) { - this->phyLayer->standby(); - if(!this->FSK) { - this->phyLayer->invertIQ(false); - } - - return(RADIOLIB_ERR_RX_TIMEOUT); - } - - // this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); - - // while(!downlinkReceived) { - while(!this->phyLayer->readIrq()) { - mod->hal->yield(); - } - - // we have a message, clear actions, go to standby and reset the IQ inversion - this->phyLayer->standby(); // TODO check: this should be done automagically due to RxSingle? - this->phyLayer->clearPacketReceivedAction(); - if(!this->FSK) { - state = this->phyLayer->invertIQ(false); - RADIOLIB_ASSERT(state); - } - // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); RADIOLIB_DEBUG_PRINTLN("Downlink message length: %d", downlinkMsgLen); @@ -1025,7 +989,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { #endif // set the MIC calculation block - // TODO implement confirmed frames memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); @@ -1118,7 +1081,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) bool isConfirmedDown = false; - if(downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] && 0xFE == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { this->confFcntDown = this->aFcntDown; } diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index b88c414b..ff1644ad 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -438,6 +438,12 @@ class LoRaWANNode { */ int16_t uplink(uint8_t* data, size_t len, uint8_t port, bool isConfirmed = false, bool adrEnabled = true); + /*! + \brief Wait for, open and listen during Rx1 and Rx2 windows; only performs listening + \returns \ref status_codes + */ + int16_t downlinkCommon(); + #if defined(RADIOLIB_BUILD_ARDUINO) /*! \brief Wait for downlink from the server in either RX1 or RX2 window. diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/src/protocols/PhysicalLayer/PhysicalLayer.cpp index d558d479..387001a0 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.cpp +++ b/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -294,23 +294,18 @@ uint32_t PhysicalLayer::getTimeOnAir(size_t len) { return(0); } -uint32_t PhysicalLayer::calculateRxTimeout(uint8_t numSymbols, DataRate_t *datarate, uint32_t offsetUs, uint32_t& timeoutUs) { +uint32_t PhysicalLayer::calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs) { (void)numSymbols; - (void)datarate; - (void)offsetUs; (void)timeoutUs; return(0); } -bool PhysicalLayer::isRxTimeout() { - return(0); +int16_t PhysicalLayer::irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask) { + (void)irqFlags; + (void)irqMask; + return(RADIOLIB_ERR_UNSUPPORTED); } -uint16_t PhysicalLayer::readIrq(bool clear) { - (void)clear; - return(0); -} - int16_t PhysicalLayer::startChannelScan() { return(RADIOLIB_ERR_UNSUPPORTED); } diff --git a/src/protocols/PhysicalLayer/PhysicalLayer.h b/src/protocols/PhysicalLayer/PhysicalLayer.h index e9f9f4da..4ffeb89c 100644 --- a/src/protocols/PhysicalLayer/PhysicalLayer.h +++ b/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -311,27 +311,21 @@ class PhysicalLayer { virtual uint32_t getTimeOnAir(size_t len); /*! - \brief Get the Rx timeout required to listen to a preamble of a certain number of symbols - \param numSymbols Number of symbols to listen for - \param datarate The datarate for which to calculate the timeout - \param offsetUs Additional offset in microseconds to allow increasing the timeout - \param timeoutUs Returns the timeout in microseconds for the host to sleep + \brief Calculate the timeout value for this specific module / series based on number of symbols or time + \param numSymbols Number of payload symbols to listen for + \param timeoutUs Timeout in microseconds to listen for \returns Timeout value in a unit that is specific for the used module */ - virtual uint32_t calculateRxTimeout(uint8_t numSymbols, DataRate_t* datarate, uint32_t offsetUs, uint32_t& timeoutUs); + virtual uint32_t calculateRxTimeout(uint8_t numSymbols, uint32_t timeoutUs); /*! - \brief Check whether there is a RxTimeout flag set - \returns RxTimeout flag is set + \brief Create the flags that make up RxDone and RxTimeout used for receiving downlinks + \param irqFlags The flags for which IRQs must be triggered + \param irqMask Mask indicating which IRQ triggers a DIO + \returns \ref status_codes */ - virtual bool isRxTimeout(); + virtual int16_t irqRxDoneRxTimeout(uint16_t &irqFlags, uint16_t &irqMask); - /*! - \brief Check whether there is a RxTimeout flag set - \returns RxTimeout flag is set - */ - virtual uint16_t readIrq(bool clear = false); - /*! \brief Interrupt-driven channel activity detection method. interrupt will be activated when packet is detected. Must be implemented in module class.