From 58eab402ad0a0b55a6890e891cbe63c4f8b72304 Mon Sep 17 00:00:00 2001 From: jgromes Date: Thu, 14 Sep 2023 20:58:46 +0200 Subject: [PATCH] [LoRaWAN] Implemented most MAC commands --- src/protocols/LoRaWAN/LoRaWAN.cpp | 236 +++++++++++++++++++++++++++++- src/protocols/LoRaWAN/LoRaWAN.h | 34 +++-- 2 files changed, 248 insertions(+), 22 deletions(-) diff --git a/src/protocols/LoRaWAN/LoRaWAN.cpp b/src/protocols/LoRaWAN/LoRaWAN.cpp index 7d5b88e0..679d76b6 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.cpp +++ b/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -32,6 +32,7 @@ LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { this->FSK = false; this->startChannel = -1; this->numChannels = -1; + this->backupFreq = this->band->backupChannel.freqStart; } void LoRaWANNode::wipe() { @@ -285,7 +286,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe // enqueue the RekeyInd MAC command to be sent in the next uplink this->rev = 1; LoRaWANMacCommand_t cmd = { - .cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, + .cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY, .len = sizeof(uint8_t), .payload = { this->rev }, .repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT, @@ -585,7 +586,7 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { } else if(i == 0) { // nothing in the first window, configure for the second - state = this->phyLayer->setFrequency(this->band->backupChannel.freqStart); + state = this->phyLayer->setFrequency(this->backupFreq); RADIOLIB_ASSERT(state); DataRate_t dataRate; @@ -1146,10 +1147,131 @@ int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQu } size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { - //RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len); + RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len); + + if(cmd->cid >= RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY) { + // TODO call user-provided callback for proprietary MAC commands? + return(cmd->len - 1); + } switch(cmd->cid) { - case(RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS): { + case(RADIOLIB_LORAWAN_MAC_CMD_RESET): { + // get the server version + uint8_t srvVersion = cmd->payload[0]; + RADIOLIB_DEBUG_PRINTLN("Server version: 1.%d", srvVersion); + if(srvVersion == this->rev) { + // valid server version, stop sending the ResetInd MAC command + popMacCommand(NULL, &this->commandsUp, true); + } + return(1); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK): { + // TODO sent by gateway as reply to node request, how to get this info to the user? + uint8_t margin = cmd->payload[0]; + uint8_t gwCnt = cmd->payload[1]; + RADIOLIB_DEBUG_PRINTLN("Link check: margin = %d dB, gwCnt = %d", margin, gwCnt); + return(2); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR): { + // get the ADR configuration + uint8_t dr = (cmd->payload[0] & 0xF0) >> 4; + uint8_t txPower = cmd->payload[0] & 0x0F; + uint16_t chMask = LoRaWANNode::ntoh(&cmd->payload[1]); + uint8_t chMaskCntl = (cmd->payload[3] & 0x70) >> 4; + uint8_t nbTrans = cmd->payload[3] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("ADR REQ: dataRate = %d, txPower = %d, chMask = 0x%04x, chMaskCntl = %02x, nbTrans = %d", dr, txPower, chMask, chMaskCntl, nbTrans); + + // apply the configuration + uint8_t drAck = 0; + if(dr != 0x0F) { + // first figure out which channel span this data rate applies to + // TODO do that by processing the chMask/chMaskCntl? + uint8_t spanChannelId = 0; + LoRaWANChannelSpan_t* span = findChannelSpan(RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, this->chIndex[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK], &spanChannelId); + + // seems to be only applicable to uplink + if(span) { + DataRate_t dataRate; + this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK] = findDataRate(dr, &dataRate, span); + if(this->phyLayer->setDataRate(dataRate) == RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PRINTLN("ADR set dr = %d channel = %d", dr, spanChannelId); + drAck = 1; + } + } + + } else { + drAck = 1; + + } + + // try to apply the power configuration + uint8_t pwrAck = 0; + if(txPower != 0x0F) { + int8_t pwr = this->band->powerMax - 2*txPower; + if(this->phyLayer->setOutputPower(pwr) == RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PRINTLN("ADR set pwr = %d", pwr); + pwrAck = 1; + } + + } else { + pwrAck = 1; + } + + // TODO implement repeated uplinks with nbTrans + // TODO implement channel mask + uint8_t chMaskAck = 0; + + // send the reply + cmd->len = 1; + cmd->payload[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); + RADIOLIB_DEBUG_PRINTLN("ADR ANS: status = 0x%02x", cmd->payload[0]); + pushMacCommand(cmd, &this->commandsUp); + return(4); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE): { + uint8_t maxDutyCycle = cmd->payload[0] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("Max duty cycle: 1/2^%d", maxDutyCycle); + + // TODO implement this + + return(1); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP): { + // get the configuration + uint8_t rx1DrOffset = (cmd->payload[0] & 0x70) >> 4; + uint8_t rx2DataRate = cmd->payload[0] & 0x0F; + uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); + float freq = (float)freqRaw/10000.0; + RADIOLIB_DEBUG_PRINTLN("RX Param: rx1DrOffset = %d, rx2DataRate = %d, freq = %f", rx1DrOffset, rx2DataRate, freq); + + // apply the configuration + this->backupFreq = freq; + float prevFreq = this->channelFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]; + uint8_t chanAck = 0; + if(this->phyLayer->setFrequency(freq) == RADIOLIB_ERR_NONE) { + chanAck = 1; + this->phyLayer->setFrequency(prevFreq); + } + + // TODO process the RX2 data rate + uint8_t rx2Ack = 0; + + // TODO process the data rate offset + uint8_t rx1OffsAck = 0; + + // send the reply + cmd->len = 1; + cmd->payload[0] = (rx1OffsAck << 2) | (rx2Ack << 1) | (chanAck << 0); + RADIOLIB_DEBUG_PRINTLN("Rx param ANS: status = 0x%02x", cmd->payload[0]); + pushMacCommand(cmd, &this->commandsUp); + return(4); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS): { // set the uplink reply cmd->len = 2; cmd->payload[1] = this->battLevel; @@ -1161,13 +1283,111 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { return(0); } break; - case(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND): { - // TODO verify the actual server version here + case(RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL): { + // get the configuration + uint8_t chIndex = cmd->payload[0]; + uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); + float freq = (float)freqRaw/10000.0; + uint8_t maxDr = (cmd->payload[4] & 0xF0) >> 4; + uint8_t minDr = cmd->payload[4] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("New channel: index = %d, freq = %f MHz, maxDr = %d, minDr = %d", chIndex, freq, maxDr, minDr); - // stop sending the ReKey MAC command - popMacCommand(NULL, &this->commandsUp, true); + // TODO implement this + + return(5); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP): { + // get the configuration + uint8_t delay = cmd->payload[0] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("RX timing: delay = %d sec", delay); + + // apply the configuration + if(delay == 0) { + delay = 1; + } + this->rxDelays[0] = delay * 1000; + this->rxDelays[1] = this->rxDelays[0] + 1000; + + // send the reply + cmd->len = 0; + + // TODO this should be sent repeatedly until the next downlink + pushMacCommand(cmd, &this->commandsUp); + return(1); } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP): { + uint8_t dlDwell = (cmd->payload[0] & 0x20) >> 5; + uint8_t ulDwell = (cmd->payload[0] & 0x10) >> 4; + uint8_t maxEirpRaw = cmd->payload[0] & 0x0F; + + // who the f came up with this ... + const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; + uint8_t maxEirp = eirpEncoding[maxEirpRaw]; + RADIOLIB_DEBUG_PRINTLN("TX timing: dlDwell = %d, dlDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, maxEirp); + + // TODO implement this + return(1); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_DL_CHANNEL): { + // get the configuration + uint8_t chIndex = cmd->payload[0]; + uint32_t freqRaw = LoRaWANNode::ntoh(&cmd->payload[1], 3); + float freq = (float)freqRaw/10000.0; + RADIOLIB_DEBUG_PRINTLN("DL channel: index = %d, freq = %f MHz", chIndex, freq); + + // TODO implement this + return(4); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_REKEY): { + // get the server version + uint8_t srvVersion = cmd->payload[0]; + RADIOLIB_DEBUG_PRINTLN("Server version: 1.%d", srvVersion); + if((srvVersion > 0) && (srvVersion <= this->rev)) { + // valid server version, stop sending the ReKey MAC command + popMacCommand(NULL, &this->commandsUp, true); + } + return(1); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP): { + // TODO implement this + uint8_t limitExp = (cmd->payload[0] & 0xF0) >> 4; + uint8_t delayExp = cmd->payload[0] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("ADR param setup: limitExp = %d, delayExp = %d", limitExp, delayExp); + return(1); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME): { + // TODO implement this - sent by gateway as reply to node request + uint32_t gpsEpoch = LoRaWANNode::ntoh(&cmd->payload[0]); + uint8_t fraction = cmd->payload[5]; + RADIOLIB_DEBUG_PRINTLN("Network time: gpsEpoch = %d s, delayExp = %f", gpsEpoch, (float)fraction/256.0f); + return(5); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_FORCE_REJOIN): { + // TODO implement this + uint16_t rejoinReq = LoRaWANNode::ntoh(&cmd->payload[0]); + uint8_t period = (rejoinReq & 0x3800) >> 11; + uint8_t maxRetries = (rejoinReq & 0x0700) >> 8; + uint8_t rejoinType = (rejoinReq & 0x0070) >> 4; + uint8_t dr = rejoinReq & 0x000F; + RADIOLIB_DEBUG_PRINTLN("Force rejoin: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); + return(2); + } break; + + case(RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP): { + // TODO implement this + uint8_t maxTime = (cmd->payload[0] & 0xF0) >> 4; + uint8_t maxCount = cmd->payload[0] & 0x0F; + RADIOLIB_DEBUG_PRINTLN("Rejoin setup: maxTime = %d, maxCount = %d", maxTime, maxCount); + return(0); + } break; } return(0); diff --git a/src/protocols/LoRaWAN/LoRaWAN.h b/src/protocols/LoRaWAN/LoRaWAN.h index b17bb7a3..e0ec0af4 100644 --- a/src/protocols/LoRaWAN/LoRaWAN.h +++ b/src/protocols/LoRaWAN/LoRaWAN.h @@ -150,20 +150,22 @@ #define RADIOLIB_LORAWAN_MAGIC (0x12AD101B) // MAC commands -#define RADIOLIB_LORAWAN_MAC_CMD_RESET_IND (0x01) -#define RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK_REQ (0x02) -#define RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR_ANS (0x03) -#define RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE_ANS (0x04) -#define RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP_ANS (0x05) -#define RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS (0x06) -#define RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL_ANS (0x07) -#define RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP_ANS (0x08) -#define RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP_ANS (0x09) -#define RADIOLIB_LORAWAN_MAC_CMD_DI_CHANNEL_ANS (0x0A) -#define RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND (0x0B) -#define RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP_ANS (0x0C) -#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME_REQ (0x0D) -#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP_ANS (0x0F) +#define RADIOLIB_LORAWAN_MAC_CMD_RESET (0x01) +#define RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK (0x02) +#define RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR (0x03) +#define RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE (0x04) +#define RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP (0x05) +#define RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS (0x06) +#define RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL (0x07) +#define RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP (0x08) +#define RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP (0x09) +#define RADIOLIB_LORAWAN_MAC_CMD_DL_CHANNEL (0x0A) +#define RADIOLIB_LORAWAN_MAC_CMD_REKEY (0x0B) +#define RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP (0x0C) +#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME (0x0D) +#define RADIOLIB_LORAWAN_MAC_CMD_FORCE_REJOIN (0x0E) +#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP (0x0F) +#define RADIOLIB_LORAWAN_MAC_CMD_PROPRIETARY (0x80) // the length of internal MAC command queue - hopefully this is enough for most use cases #define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (8) @@ -423,6 +425,10 @@ class LoRaWANNode { // currently configured channel for uplink and downlink (band-dependent!) uint8_t chIndex[2] = { 0 }; + // backup channel properties - may be changed by MAC command + float backupFreq = 0; + uint8_t backupDataRate = 0; + // timestamp to measure the RX1/2 delay (from uplink end) uint32_t rxDelayStart = 0;