[LoRaWAN] Implemented MAC command support
This commit is contained in:
parent
73382c2933
commit
16f0ba7cce
6 changed files with 206 additions and 35 deletions
|
@ -133,9 +133,13 @@ void loop() {
|
|||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
|
||||
// print data of the packet
|
||||
// print data of the packet (if there are any)
|
||||
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||
Serial.println(strDown);
|
||||
if(strDown.length() > 0) {
|
||||
Serial.println(strDown);
|
||||
} else {
|
||||
Serial.println(F("<MAC commands only>"));
|
||||
}
|
||||
|
||||
// print RSSI (Received Signal Strength Indicator)
|
||||
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||
|
|
|
@ -128,9 +128,13 @@ void loop() {
|
|||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
|
||||
// print data of the packet
|
||||
// print data of the packet (if there are any)
|
||||
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||
Serial.println(strDown);
|
||||
if(strDown.length() > 0) {
|
||||
Serial.println(strDown);
|
||||
} else {
|
||||
Serial.println(F("<MAC commands only>"));
|
||||
}
|
||||
|
||||
// print RSSI (Received Signal Strength Indicator)
|
||||
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||
|
|
|
@ -399,3 +399,5 @@ RADIOLIB_ERR_INVALID_PORT LITERAL1
|
|||
RADIOLIB_ERR_NO_RX_WINDOW LITERAL1
|
||||
RADIOLIB_ERR_INVALID_CHANNEL LITERAL1
|
||||
RADIOLIB_ERR_INVALID_CID LITERAL1
|
||||
RADIOLIB_ERR_COMMAND_QUEUE_FULL LITERAL1
|
||||
RADIOLIB_ERR_COMMAND_QUEUE_EMPTY LITERAL1
|
||||
|
|
|
@ -523,6 +523,16 @@
|
|||
*/
|
||||
#define RADIOLIB_ERR_UPLINK_UNAVAILABLE (-1108)
|
||||
|
||||
/*!
|
||||
\brief Unable to push new MAC command because the queue is full.
|
||||
*/
|
||||
#define RADIOLIB_ERR_COMMAND_QUEUE_FULL (-1109)
|
||||
|
||||
/*!
|
||||
\brief Unable to pop existing MAC command because the queue is empty.
|
||||
*/
|
||||
#define RADIOLIB_ERR_COMMAND_QUEUE_EMPTY (-1110)
|
||||
|
||||
/*!
|
||||
\}
|
||||
*/
|
||||
|
|
|
@ -250,17 +250,17 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
|
|||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
|
||||
//Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
// send the RekeyInd MAC command
|
||||
// enqueue the RekeyInd MAC command to be sent in the next uplink
|
||||
this->rev = 1;
|
||||
uint8_t serverRev = 0xFF;
|
||||
state = sendMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, &this->rev, sizeof(uint8_t), &serverRev, sizeof(uint8_t));
|
||||
LoRaWANMacCommand_t cmd = {
|
||||
.cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND,
|
||||
.len = sizeof(uint8_t),
|
||||
.payload = { this->rev },
|
||||
.repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT,
|
||||
};
|
||||
state = pushMacCommand(&cmd, &this->commandsUp);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// check the supported server version
|
||||
if(serverRev != this->rev) {
|
||||
return(RADIOLIB_ERR_INVALID_REVISION);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 1.0 version, just derive the keys
|
||||
this->rev = 0;
|
||||
|
@ -329,10 +329,11 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
return(RADIOLIB_ERR_INVALID_PORT);
|
||||
}
|
||||
|
||||
// check if there is a MAC command to piggyback
|
||||
uint8_t foptsLen = 0;
|
||||
if(this->command) {
|
||||
foptsLen = 1 + this->command->len;
|
||||
// check if there are some MAC commands to piggyback
|
||||
size_t foptsLen = 0;
|
||||
if(this->commandsUp.numCommands > 0) {
|
||||
// there are, assume the maximum possible FOpts len for buffer allocation
|
||||
foptsLen = 15;
|
||||
}
|
||||
|
||||
// check maximum payload len as defined in phy
|
||||
|
@ -361,24 +362,30 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr);
|
||||
|
||||
// TODO implement adaptive data rate
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00 | foptsLen;
|
||||
// foptslen will be added later
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00;
|
||||
|
||||
// get frame counter from persistent storage
|
||||
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1;
|
||||
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt);
|
||||
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt);
|
||||
|
||||
// check if there is something in FOpts
|
||||
if(this->command) {
|
||||
// append MAC command
|
||||
// check if we have some MAC command to append
|
||||
// TODO implement appending multiple MAC commands
|
||||
LoRaWANMacCommand_t cmd = { 0 };
|
||||
if(popMacCommand(&cmd, &this->commandsUp) == RADIOLIB_ERR_NONE) {
|
||||
// we do, add it to fopts
|
||||
uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE];
|
||||
foptsBuff[0] = this->command->cid;
|
||||
for(size_t i = 1; i < this->command->len; i++) {
|
||||
foptsBuff[i] = this->command->payload[i];
|
||||
foptsBuff[0] = cmd.cid;
|
||||
for(size_t i = 1; i < cmd.len; i++) {
|
||||
foptsBuff[i] = cmd.payload[i];
|
||||
}
|
||||
foptsLen = 1 + cmd.len;
|
||||
uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen;
|
||||
|
||||
// encrypt it
|
||||
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, false);
|
||||
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true);
|
||||
}
|
||||
|
||||
// set the port
|
||||
|
@ -435,7 +442,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set the timestamp so that we can measure when to start receiving
|
||||
this->command = NULL;
|
||||
this->rxDelayStart = txStart + timeOnAir;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
@ -656,21 +662,46 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
|
|||
uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
|
||||
if(foptsLen > 0) {
|
||||
// there are some Fopts, decrypt them
|
||||
*len = foptsLen;
|
||||
uint8_t fopts[RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK];
|
||||
|
||||
// according to the specification, the last two arguments should be 0x00 and false,
|
||||
// but that will fail even for LoRaWAN 1.1.0 server
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], foptsLen, this->nwkSEncKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true);
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true);
|
||||
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
//Module::hexdump(fopts, foptsLen);
|
||||
|
||||
// process the MAC command(s)
|
||||
int8_t remLen = foptsLen;
|
||||
uint8_t* foptsPtr = fopts;
|
||||
while(remLen > 0) {
|
||||
LoRaWANMacCommand_t cmd = {
|
||||
.cid = *foptsPtr,
|
||||
.len = remLen - 1,
|
||||
.payload = { 0 },
|
||||
};
|
||||
memcpy(cmd.payload, foptsPtr + 1, cmd.len);
|
||||
|
||||
// try to process the mac command
|
||||
// TODO how to handle incomplete commands?
|
||||
size_t processedLen = execMacCommand(&cmd) + 1;
|
||||
|
||||
// processing succeeded, move in the buffer to the next command
|
||||
remLen -= processedLen;
|
||||
foptsPtr += processedLen;
|
||||
}
|
||||
}
|
||||
|
||||
// fopts are processed or not present, check if there is payload
|
||||
int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t);
|
||||
if(payLen <= 0) {
|
||||
// no payload
|
||||
*len = 0;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
// no fopts, just payload
|
||||
// TODO implement decoding piggybacked Fopts?
|
||||
*len = downlinkMsgLen;
|
||||
// there is payload, and so there should be a port too
|
||||
// TODO pass the port?
|
||||
*len = payLen - 1;
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
|
||||
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
|
@ -680,6 +711,10 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
|
|||
return(state);
|
||||
}
|
||||
|
||||
void LoRaWANNode::setDeviceStatus(uint8_t battLevel) {
|
||||
this->battLevel = battLevel;
|
||||
}
|
||||
|
||||
void LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span) {
|
||||
uint8_t dataRateBand = span->dataRates[dr];
|
||||
this->dataRate = dr;
|
||||
|
@ -905,6 +940,82 @@ int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloa
|
|||
return(state);
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue) {
|
||||
if(queue->numCommands >= RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) {
|
||||
return(RADIOLIB_ERR_COMMAND_QUEUE_FULL);
|
||||
}
|
||||
|
||||
memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t));
|
||||
/*RADIOLIB_DEBUG_PRINTLN("push MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
|
||||
queue->commands[queue->numCommands - 1].cid,
|
||||
queue->commands[queue->numCommands - 1].len,
|
||||
queue->commands[queue->numCommands - 1].payload[0],
|
||||
queue->commands[queue->numCommands - 1].payload[1],
|
||||
queue->commands[queue->numCommands - 1].payload[2],
|
||||
queue->commands[queue->numCommands - 1].payload[3],
|
||||
queue->commands[queue->numCommands - 1].payload[4],
|
||||
queue->commands[queue->numCommands - 1].repeat);*/
|
||||
queue->numCommands++;
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force) {
|
||||
if(queue->numCommands == 0) {
|
||||
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
|
||||
}
|
||||
|
||||
if(cmd) {
|
||||
/*RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
|
||||
queue->commands[queue->numCommands - 1].cid,
|
||||
queue->commands[queue->numCommands - 1].len,
|
||||
queue->commands[queue->numCommands - 1].payload[0],
|
||||
queue->commands[queue->numCommands - 1].payload[1],
|
||||
queue->commands[queue->numCommands - 1].payload[2],
|
||||
queue->commands[queue->numCommands - 1].payload[3],
|
||||
queue->commands[queue->numCommands - 1].payload[4],
|
||||
queue->commands[queue->numCommands - 1].repeat);*/
|
||||
memcpy(cmd, &queue->commands[queue->numCommands - 1], sizeof(LoRaWANMacCommand_t));
|
||||
}
|
||||
|
||||
if((!force) && (queue->commands[queue->numCommands - 1].repeat > 0)) {
|
||||
queue->commands[queue->numCommands - 1].repeat--;
|
||||
} else {
|
||||
queue->commands[queue->numCommands - 1].repeat = 0;
|
||||
queue->numCommands--;
|
||||
}
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
|
||||
//RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len);
|
||||
|
||||
switch(cmd->cid) {
|
||||
case(RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS): {
|
||||
// set the uplink reply
|
||||
cmd->len = 2;
|
||||
cmd->payload[1] = this->battLevel;
|
||||
int8_t snr = this->phyLayer->getSNR();
|
||||
cmd->payload[0] = snr & 0x3F;
|
||||
|
||||
// push it to the uplink queue
|
||||
pushMacCommand(cmd, &this->commandsUp);
|
||||
return(0);
|
||||
} break;
|
||||
|
||||
case(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND): {
|
||||
// TODO verify the actual server version here
|
||||
|
||||
// stop sending the ReKey MAC command
|
||||
popMacCommand(NULL, &this->commandsUp, true);
|
||||
return(1);
|
||||
} break;
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter) {
|
||||
// figure out how many encryption blocks are there
|
||||
size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE;
|
||||
|
|
|
@ -160,6 +160,9 @@
|
|||
#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME_REQ (0x0D)
|
||||
#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP_ANS (0x0F)
|
||||
|
||||
// the length of internal MAC command queue - hopefully this is enough for most use cases
|
||||
#define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (8)
|
||||
|
||||
/*!
|
||||
\struct LoRaWANChannelSpan_t
|
||||
\brief Structure to save information about LoRaWAN channels.
|
||||
|
@ -235,10 +238,27 @@ extern const LoRaWANBand_t AS923;
|
|||
extern const LoRaWANBand_t KR920;
|
||||
extern const LoRaWANBand_t IN865;
|
||||
|
||||
/*!
|
||||
\struct LoRaWANMacCommand_t
|
||||
\brief Structure to save information about MAC command
|
||||
*/
|
||||
struct LoRaWANMacCommand_t {
|
||||
/*! \brief The command ID */
|
||||
uint8_t cid;
|
||||
|
||||
/*! \brief Length of the payload */
|
||||
size_t len;
|
||||
uint8_t* payload;
|
||||
|
||||
/*! \brief Payload buffer (5 bytes is the longest possible) */
|
||||
uint8_t payload[5];
|
||||
|
||||
/*! \brief Repetition counter (the command will be uplinked repeat + 1 times) */
|
||||
uint8_t repeat;
|
||||
};
|
||||
|
||||
struct LoRaWANMacCommandQueue_t {
|
||||
LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE];
|
||||
size_t numCommands;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -337,13 +357,21 @@ class LoRaWANNode {
|
|||
*/
|
||||
int16_t downlink(uint8_t* data, size_t* len);
|
||||
|
||||
/*!
|
||||
\brief Set device status.
|
||||
\param battLevel Battery level to set. 0 for external power source, 1 for lowest battery,
|
||||
254 for highest battery, 255 for unable to measure.
|
||||
*/
|
||||
void setDeviceStatus(uint8_t battLevel);
|
||||
|
||||
#if !defined(RADIOLIB_GODMODE)
|
||||
private:
|
||||
#endif
|
||||
PhysicalLayer* phyLayer = NULL;
|
||||
const LoRaWANBand_t* band = NULL;
|
||||
|
||||
LoRaWANMacCommand_t* command = NULL;
|
||||
LoRaWANMacCommandQueue_t commandsUp = { .commands = { 0 }, .numCommands = 0 };
|
||||
LoRaWANMacCommandQueue_t commandsDown = { .commands = { 0 }, .numCommands = 0 };
|
||||
|
||||
// the following is either provided by the network server (OTAA)
|
||||
// or directly entered by the user (ABP)
|
||||
|
@ -371,6 +399,9 @@ class LoRaWANNode {
|
|||
// delays between the uplink and RX1/2 windows
|
||||
uint32_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS };
|
||||
|
||||
// device status - battery level
|
||||
uint8_t battLevel = 0xFF;
|
||||
|
||||
// find the first usable data rate in a given channel span
|
||||
void findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span);
|
||||
|
||||
|
@ -395,6 +426,15 @@ class LoRaWANNode {
|
|||
// send a MAC command to the network server
|
||||
int16_t sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen);
|
||||
|
||||
// push MAC command to queue, done by copy
|
||||
int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue);
|
||||
|
||||
// pop MAC command from queue, done by copy unless CMD is NULL
|
||||
int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force = false);
|
||||
|
||||
// execute mac command, return the number of processed bytes for sequential processing
|
||||
size_t execMacCommand(LoRaWANMacCommand_t* cmd);
|
||||
|
||||
// function to encrypt and decrypt payloads
|
||||
void processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue