[LoRaWAN] Added support for 1.1 and downlink (#58)
This commit is contained in:
parent
d34902ee46
commit
cd9ac9916c
5 changed files with 719 additions and 169 deletions
|
@ -60,10 +60,10 @@ void setup() {
|
|||
// the end device in TTN and perform the join procedure again!
|
||||
//node.wipe();
|
||||
|
||||
// application identifier - in LoRaWAN 1.1, it is also called joinEUI
|
||||
// application identifier - pre-LoRaWAN 1.1.0, this was called appEUI
|
||||
// when adding new end device in TTN, you will have to enter this number
|
||||
// you can pick any number you want, but it has to be unique
|
||||
uint64_t appEUI = 0x12AD1011B0C0FFEE;
|
||||
uint64_t joinEUI = 0x12AD1011B0C0FFEE;
|
||||
|
||||
// device identifier - this number can be anything
|
||||
// when adding new end device in TTN, you can generate this number,
|
||||
|
@ -76,10 +76,14 @@ void setup() {
|
|||
const char nwkKey[] = "topSecretKey1234";
|
||||
const char appKey[] = "aDifferentKeyABC";
|
||||
|
||||
// prior to LoRaWAN 1.1.0, only a single "nwkKey" is used
|
||||
// when connecting to LoRaWAN 1.0 network, "appKey" will be disregarded
|
||||
// and can be set to NULL
|
||||
|
||||
// now we can start the activation
|
||||
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range
|
||||
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
||||
state = node.beginOTAA(appEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
|
||||
state = node.beginOTAA(joinEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
|
@ -110,8 +114,8 @@ int count = 0;
|
|||
void loop() {
|
||||
// send uplink to port 10
|
||||
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
||||
String str = "Hello World! #" + String(count++);
|
||||
int state = node.uplink(str, 10);
|
||||
String strUp = "Hello World! #" + String(count++);
|
||||
int state = node.uplink(strUp, 10);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
|
@ -119,6 +123,43 @@ void loop() {
|
|||
Serial.println(state);
|
||||
}
|
||||
|
||||
// wait before sending another one
|
||||
// after uplink, you can call downlink(),
|
||||
// to receive any possible reply from the server
|
||||
// this function must be called within a few seconds
|
||||
// after uplink to receive the downlink!
|
||||
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
|
||||
String strDown;
|
||||
state = node.downlink(strDown);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
|
||||
// print data of the packet
|
||||
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||
Serial.println(strDown);
|
||||
|
||||
// print RSSI (Received Signal Strength Indicator)
|
||||
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||
Serial.print(radio.getRSSI());
|
||||
Serial.println(F(" dBm"));
|
||||
|
||||
// print SNR (Signal-to-Noise Ratio)
|
||||
Serial.print(F("[LoRaWAN] SNR:\t\t"));
|
||||
Serial.print(radio.getSNR());
|
||||
Serial.println(F(" dB"));
|
||||
|
||||
// print frequency error
|
||||
Serial.print(F("[LoRaWAN] Frequency error:\t"));
|
||||
Serial.print(radio.getFrequencyError());
|
||||
Serial.println(F(" Hz"));
|
||||
|
||||
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||
Serial.println(F("timeout!"));
|
||||
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
// wait before sending another packet
|
||||
delay(10000);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,10 @@ void setup() {
|
|||
const char nwkSKey[] = "topSecretKey1234";
|
||||
const char appSKey[] = "aDifferentKeyABC";
|
||||
|
||||
// prior to LoRaWAN 1.1.0, only a single "nwkKey" is used
|
||||
// when connecting to LoRaWAN 1.0 network, "appKey" will be disregarded
|
||||
// and can be set to NULL
|
||||
|
||||
// start the device by directly providing the encryption keys and device address
|
||||
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
|
||||
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey);
|
||||
|
@ -105,8 +109,8 @@ int count = 0;
|
|||
void loop() {
|
||||
// send uplink to port 10
|
||||
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
|
||||
String str = "Hello World! #" + String(count++);
|
||||
int state = node.uplink(str, 10);
|
||||
String strUp = "Hello World! #" + String(count++);
|
||||
int state = node.uplink(strUp, 10);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
|
@ -114,6 +118,43 @@ void loop() {
|
|||
Serial.println(state);
|
||||
}
|
||||
|
||||
// wait before sending another one
|
||||
// after uplink, you can call downlink(),
|
||||
// to receive any possible reply from the server
|
||||
// this function must be called within a few seconds
|
||||
// after uplink to receive the downlink!
|
||||
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
|
||||
String strDown;
|
||||
state = node.downlink(strDown);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
|
||||
// print data of the packet
|
||||
Serial.print(F("[LoRaWAN] Data:\t\t"));
|
||||
Serial.println(strDown);
|
||||
|
||||
// print RSSI (Received Signal Strength Indicator)
|
||||
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
||||
Serial.print(radio.getRSSI());
|
||||
Serial.println(F(" dBm"));
|
||||
|
||||
// print SNR (Signal-to-Noise Ratio)
|
||||
Serial.print(F("[LoRaWAN] SNR:\t\t"));
|
||||
Serial.print(radio.getSNR());
|
||||
Serial.println(F(" dB"));
|
||||
|
||||
// print frequency error
|
||||
Serial.print(F("[LoRaWAN] Frequency error:\t"));
|
||||
Serial.print(radio.getFrequencyError());
|
||||
Serial.println(F(" Hz"));
|
||||
|
||||
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||
Serial.println(F("timeout!"));
|
||||
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
// wait before sending another packet
|
||||
delay(10000);
|
||||
}
|
||||
|
|
|
@ -292,6 +292,7 @@ wipe KEYWORD2
|
|||
beginOTAA KEYWORD2
|
||||
beginAPB KEYWORD2
|
||||
uplink KEYWORD2
|
||||
downlink KEYWORD2
|
||||
configureChannel KEYWORD2
|
||||
|
||||
#######################################
|
||||
|
|
|
@ -15,6 +15,17 @@ static void LoRaWANNodeOnDownlink(void) {
|
|||
downlinkReceived = true;
|
||||
}
|
||||
|
||||
// flag to indicate whether channel scan operation is complete
|
||||
static volatile bool scanFlag = false;
|
||||
|
||||
// interrupt service routine to handle downlinks automatically
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
IRAM_ATTR
|
||||
#endif
|
||||
static void LoRaWANNodeOnChannelScan(void) {
|
||||
scanFlag = true;
|
||||
}
|
||||
|
||||
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
|
||||
this->phyLayer = phy;
|
||||
this->band = band;
|
||||
|
@ -34,12 +45,11 @@ int16_t LoRaWANNode::begin() {
|
|||
Module* mod = this->phyLayer->getMod();
|
||||
if(mod->hal->getPersistentParameter<uint32_t>(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_CHIP_NOT_FOUND);
|
||||
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
|
||||
}
|
||||
|
||||
// pull all needed information from persistent storage
|
||||
this->devAddr = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID);
|
||||
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", this->devAddr);
|
||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
@ -47,7 +57,7 @@ int16_t LoRaWANNode::begin() {
|
|||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
|
||||
int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
|
||||
// check if we actually need to send the join request
|
||||
Module* mod = this->phyLayer->getMod();
|
||||
if(!force && (mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) {
|
||||
|
@ -62,14 +72,13 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
|||
// get dev nonce from persistent storage and increment it
|
||||
uint16_t devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID);
|
||||
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1);
|
||||
RADIOLIB_DEBUG_PRINTLN("devNonce = %d", devNonce);
|
||||
|
||||
// build the join-request message
|
||||
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
|
||||
|
||||
// set the packet fields
|
||||
joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
||||
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], appEUI);
|
||||
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI);
|
||||
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI);
|
||||
LoRaWANNode::hton<uint16_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce);
|
||||
|
||||
|
@ -85,6 +94,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
|||
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);
|
||||
|
@ -122,7 +132,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
|||
size_t lenRx = this->phyLayer->getPacketLength(true);
|
||||
if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) {
|
||||
RADIOLIB_DEBUG_PRINTLN("joinAccept reply length mismatch, expected %luB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, lenRx);
|
||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// read the packet
|
||||
|
@ -136,30 +146,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
|||
// check reply message type
|
||||
if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) {
|
||||
RADIOLIB_DEBUG_PRINTLN("joinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]);
|
||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// decrypt the join accept message
|
||||
// this is done by encrypting again in ECB mode
|
||||
// the first byte is the MAC header which is not encrpyted
|
||||
// the first byte is the MAC header which is not encrypted
|
||||
uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
|
||||
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]);
|
||||
|
||||
//Module::hexdump(joinAcceptMsg, RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN);
|
||||
//Module::hexdump(joinAcceptMsg, lenRx);
|
||||
|
||||
// check LoRaWAN revision (the MIC verification depends on this)
|
||||
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
|
||||
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
|
||||
// 1.1 version, first we need to derive the join accept integrity key
|
||||
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY;
|
||||
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[1], devEUI);
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->jSIntKey);
|
||||
|
||||
// prepare the buffer for MIC calculation
|
||||
uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE;
|
||||
LoRaWANNode::hton<uint64_t>(&micBuff[1], joinEUI);
|
||||
LoRaWANNode::hton<uint16_t>(&micBuff[9], devNonce);
|
||||
memcpy(&micBuff[11], joinAcceptMsg, lenRx);
|
||||
|
||||
//Module::hexdump(micBuff, lenRx + 11);
|
||||
|
||||
if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) {
|
||||
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 1.0 version
|
||||
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
|
||||
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||
}
|
||||
|
||||
// verify MIC
|
||||
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
|
||||
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||
}
|
||||
|
||||
// parse the contents
|
||||
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
|
||||
uint32_t homeNetId = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3);
|
||||
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]);
|
||||
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
|
||||
this->rxDelay = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
|
||||
this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000;
|
||||
if(this->rxDelays[0] == 0) {
|
||||
this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS;
|
||||
}
|
||||
this->rxDelays[1] = this->rxDelays[0] + 1000;
|
||||
|
||||
// process CFlist if present
|
||||
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
|
||||
|
@ -174,35 +213,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
|
|||
} else {
|
||||
// TODO list of masks
|
||||
RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)");
|
||||
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
|
||||
return(RADIOLIB_ERR_UNSUPPORTED);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RADIOLIB_DEBUG_PRINTLN("joinNonce = %lu", joinNonce);
|
||||
RADIOLIB_DEBUG_PRINTLN("homeNetId = %lu", homeNetId);
|
||||
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", devAddr);
|
||||
RADIOLIB_DEBUG_PRINTLN("dlSettings = 0x%02x", dlSettings);
|
||||
RADIOLIB_DEBUG_PRINTLN("rxDelay = %d", this->rxDelay);
|
||||
|
||||
// prepare buffer for key derivation
|
||||
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3);
|
||||
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
|
||||
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
|
||||
|
||||
// check protocol version (1.0 vs 1.1)
|
||||
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
|
||||
// TODO implement 1.1
|
||||
// 1.1 version, derive the keys
|
||||
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI);
|
||||
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce);
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
|
||||
//Module::hexdump(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
RadioLibAES128Instance.init(appKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
|
||||
//Module::hexdump(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
|
||||
//Module::hexdump(this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY;
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey);
|
||||
//Module::hexdump(this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY;
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
|
||||
//Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
|
||||
|
||||
// send the RekeyInd MAC command
|
||||
this->rev = 1;
|
||||
RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)");
|
||||
(void)appKey;
|
||||
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
|
||||
uint8_t serverRev = 0xFF;
|
||||
state = sendMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, &this->rev, sizeof(uint8_t), &serverRev, sizeof(uint8_t));
|
||||
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;
|
||||
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
|
||||
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
|
||||
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
|
||||
RadioLibAES128Instance.init(nwkKey);
|
||||
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
|
||||
|
@ -262,18 +325,28 @@ int16_t LoRaWANNode::uplink(const char* str, uint8_t port) {
|
|||
|
||||
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
||||
// check destination port
|
||||
RADIOLIB_CHECK_RANGE(port, 0x01, 0xDF, RADIOLIB_ERR_INVALID_PAYLOAD);
|
||||
RADIOLIB_CHECK_RANGE(port, 0x00, 0xDF, RADIOLIB_ERR_INVALID_PORT);
|
||||
|
||||
// check maximum payload len as defiend in phy
|
||||
// TODO implement Fopts
|
||||
// check if there is a MAC command to piggyback
|
||||
uint8_t foptsLen = 0;
|
||||
if(this->command) {
|
||||
foptsLen = 1 + this->command->len;
|
||||
}
|
||||
|
||||
// check maximum payload len as defined in phy
|
||||
if(len > this->band->payloadLenMax[this->dataRate]) {
|
||||
return(RADIOLIB_ERR_PACKET_TOO_LONG);
|
||||
}
|
||||
|
||||
// check if sufficient time has elapsed since the last uplink
|
||||
Module* mod = this->phyLayer->getMod();
|
||||
/*if(mod->hal->millis() - this->rxDelayStart < ) {
|
||||
|
||||
}*/
|
||||
|
||||
// build the uplink message
|
||||
// the first 16 bytes are reserved for MIC calculation blocks
|
||||
size_t uplinkMsgLen = RADIOLIB_LORAWAN_UPLINK_LEN(len, foptsLen);
|
||||
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
|
||||
#if defined(RADIOLIB_STATIC_ONLY)
|
||||
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#else
|
||||
|
@ -281,62 +354,41 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
#endif
|
||||
|
||||
// set the packet fields
|
||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS], this->devAddr);
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
|
||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr);
|
||||
|
||||
// TODO implement adaptive data rate
|
||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCTRL_POS] = 0x00;
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00 | foptsLen;
|
||||
|
||||
// get frame counter from persistent storage
|
||||
Module* mod = this->phyLayer->getMod();
|
||||
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID);
|
||||
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt + 1);
|
||||
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCNT_POS], (uint16_t)fcnt);
|
||||
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);
|
||||
|
||||
// TODO implement FOpts
|
||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port;
|
||||
// check if there is something in FOpts
|
||||
if(this->command) {
|
||||
// append MAC command
|
||||
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];
|
||||
}
|
||||
|
||||
// encrypt it
|
||||
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, false);
|
||||
}
|
||||
|
||||
// set the port
|
||||
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(foptsLen)] = port;
|
||||
|
||||
// select encryption key based on the target port
|
||||
uint8_t* encKey = this->appSKey;
|
||||
if(port == 0) {
|
||||
if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
|
||||
encKey = this->nwkSEncKey;
|
||||
}
|
||||
|
||||
// figure out how many encryption blocks are there
|
||||
size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE;
|
||||
if(len % RADIOLIB_AES128_BLOCK_SIZE) {
|
||||
numBlocks++;
|
||||
}
|
||||
|
||||
// generate the encryption blocks
|
||||
uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC;
|
||||
encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK;
|
||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
|
||||
|
||||
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
||||
|
||||
// now encrypt the payload
|
||||
size_t remLen = len;
|
||||
for(size_t i = 0; i < numBlocks; i++) {
|
||||
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
|
||||
|
||||
// encrypt the buffer
|
||||
RadioLibAES128Instance.init(encKey);
|
||||
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
|
||||
|
||||
// now xor the buffer with the payload
|
||||
size_t xorLen = remLen;
|
||||
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
|
||||
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
|
||||
}
|
||||
for(uint8_t j = 0; j < xorLen; j++) {
|
||||
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(foptsLen) + i*RADIOLIB_AES128_BLOCK_SIZE + j] = data[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
|
||||
}
|
||||
remLen -= xorLen;
|
||||
}
|
||||
// encrypt the frame payload
|
||||
processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true);
|
||||
|
||||
// create blocks for MIC calculation
|
||||
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
|
@ -362,7 +414,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
|
||||
// check LoRaWAN revision
|
||||
if(this->rev == 1) {
|
||||
uint32_t mic = ((uint32_t)(micS & 0x00FF) << 24) | ((uint32_t)(micS & 0xFF00) << 8) | ((uint32_t)(micF & 0xFF00) >> 8) | ((uint32_t)(micF & 0x00FF) << 8);
|
||||
uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0);
|
||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic);
|
||||
} else {
|
||||
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF);
|
||||
|
@ -371,17 +423,297 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
|
|||
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
||||
|
||||
// send it (without the MIC calculation blocks)
|
||||
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS);
|
||||
uint32_t txStart = mod->hal->millis();
|
||||
uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000;
|
||||
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS);
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] uplinkMsg;
|
||||
#endif
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// TODO implement listening for downlinks in RX1/RX2 slots
|
||||
|
||||
// set the timestamp so that we can measure when to start receiving
|
||||
this->command = NULL;
|
||||
this->rxDelayStart = txStart + timeOnAir;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
#if defined(RADIOLIB_BUILD_ARDUINO)
|
||||
int16_t LoRaWANNode::downlink(String& str) {
|
||||
int16_t state = RADIOLIB_ERR_NONE;
|
||||
|
||||
// build a temporary buffer
|
||||
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
|
||||
size_t length = 0;
|
||||
uint8_t data[251];
|
||||
|
||||
// wait for downlink
|
||||
state = this->downlink(data, &length);
|
||||
if(state == RADIOLIB_ERR_NONE) {
|
||||
// add null terminator
|
||||
data[length] = '\0';
|
||||
|
||||
// initialize Arduino String class
|
||||
str = String((char*)data);
|
||||
}
|
||||
|
||||
return(state);
|
||||
}
|
||||
#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 = 500;
|
||||
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);
|
||||
}
|
||||
|
||||
// downlink messages are sent with inverted IQ
|
||||
int16_t state = RADIOLIB_ERR_UNKNOWN;
|
||||
if(!this->FSK) {
|
||||
state = this->phyLayer->invertIQ(true);
|
||||
RADIOLIB_ASSERT(state);
|
||||
}
|
||||
|
||||
// calculate the channel scanning timeout
|
||||
// according to the spec, this must be at least enough time to effectively detect a preamble
|
||||
uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000;
|
||||
|
||||
// set up everything for channel scan
|
||||
downlinkReceived = false;
|
||||
scanFlag = false;
|
||||
bool packetDetected = false;
|
||||
this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan);
|
||||
|
||||
// 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);
|
||||
|
||||
// wait until we get a preamble
|
||||
uint32_t scanStart = mod->hal->millis();
|
||||
while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) {
|
||||
// check channel detection timeout
|
||||
state = this->phyLayer->startChannelScan();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// wait with some timeout, though it should not be hit
|
||||
uint32_t cadStart = mod->hal->millis();
|
||||
while(!scanFlag) {
|
||||
mod->hal->yield();
|
||||
if(mod->hal->millis() - cadStart >= 3000) {
|
||||
// timed out, stop waiting
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check the scan result
|
||||
scanFlag = false;
|
||||
state = this->phyLayer->getChannelScanResult();
|
||||
if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) {
|
||||
packetDetected = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if we have a packet
|
||||
if(packetDetected) {
|
||||
break;
|
||||
|
||||
} else if(i == 0) {
|
||||
// nothing in the first window, configure for the second
|
||||
state = this->phyLayer->setFrequency(this->band->backupChannel.freqStart);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
DataRate_t datr;
|
||||
findDataRate(RADIOLIB_LORAWAN_DATA_RATE_UNUSED, &datr, &this->band->backupChannel);
|
||||
state = this->phyLayer->setDataRate(datr);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if we received a packet at all
|
||||
if(!packetDetected) {
|
||||
this->phyLayer->standby();
|
||||
if(!this->FSK) {
|
||||
this->phyLayer->invertIQ(false);
|
||||
}
|
||||
|
||||
// restore the original uplink channel
|
||||
this->configureChannel(0, this->dataRate);
|
||||
|
||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||
}
|
||||
|
||||
// channel scan is finished, swap the actions
|
||||
this->phyLayer->clearChannelScanAction();
|
||||
downlinkReceived = false;
|
||||
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
|
||||
|
||||
// start receiving
|
||||
state = this->phyLayer->startReceive();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// wait for reception with some timeout
|
||||
uint32_t rxStart = mod->hal->millis();
|
||||
while(!downlinkReceived) {
|
||||
mod->hal->yield();
|
||||
// let's hope 30 seconds is long enough timeout
|
||||
if(mod->hal->millis() - rxStart >= 30000) {
|
||||
// timed out
|
||||
this->phyLayer->standby();
|
||||
if(!this->FSK) {
|
||||
this->phyLayer->invertIQ(false);
|
||||
}
|
||||
return(RADIOLIB_ERR_RX_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
// we have a message, clear actions, go to standby and reset the IQ inversion
|
||||
downlinkReceived = false;
|
||||
this->phyLayer->standby();
|
||||
this->phyLayer->clearPacketReceivedAction();
|
||||
if(!this->FSK) {
|
||||
state = this->phyLayer->invertIQ(false);
|
||||
RADIOLIB_ASSERT(state);
|
||||
}
|
||||
|
||||
// get the packet length
|
||||
size_t downlinkMsgLen = this->phyLayer->getPacketLength();
|
||||
|
||||
// check the minimum required frame length
|
||||
// an extra byte is subtracted because downlink frames may not have a port
|
||||
if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) {
|
||||
RADIOLIB_DEBUG_PRINTLN("Downlink message too short (%lu bytes)", downlinkMsgLen);
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// build the buffer for the downlink message
|
||||
// the first 16 bytes are reserved for MIC calculation block
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen];
|
||||
#else
|
||||
uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#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<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
||||
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK;
|
||||
downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t);
|
||||
|
||||
// read the data
|
||||
state = this->phyLayer->readData(&downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE], downlinkMsgLen);
|
||||
// downlink frames are sent without CRC, which will raise error on SX127x
|
||||
// we can ignore that error
|
||||
if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) {
|
||||
state = RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
//Module::hexdump(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
|
||||
|
||||
if(state != RADIOLIB_ERR_NONE) {
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(state);
|
||||
}
|
||||
|
||||
// check the MIC
|
||||
if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) {
|
||||
return(RADIOLIB_ERR_CRC_MISMATCH);
|
||||
}
|
||||
|
||||
// check the address
|
||||
uint32_t addr = LoRaWANNode::ntoh<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]);
|
||||
if(addr != this->devAddr) {
|
||||
RADIOLIB_DEBUG_PRINTLN("Device address mismatch, expected 0x%08X, got 0x%08X", this->devAddr, addr);
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// TODO cache the ADR bit?
|
||||
// TODO cache and check fcnt?
|
||||
uint16_t fcnt = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
|
||||
|
||||
// check fopts 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;
|
||||
|
||||
// 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);
|
||||
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
// no fopts, just payload
|
||||
// TODO implement decoding piggybacked Fopts?
|
||||
*len = downlinkMsgLen;
|
||||
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
|
||||
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] downlinkMsg;
|
||||
#endif
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
void LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span) {
|
||||
uint8_t dataRateBand = span->dataRates[dr];
|
||||
this->dataRate = dr;
|
||||
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
|
||||
if(span->dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||
dataRateBand = span->dataRates[i];
|
||||
this->dataRate = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
|
||||
datr->fsk.bitRate = 50;
|
||||
datr->fsk.freqDev = 25;
|
||||
|
||||
} else {
|
||||
uint8_t bw = dataRateBand & 0x03;
|
||||
switch(bw) {
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
|
||||
datr->lora.bandwidth = 125.0;
|
||||
break;
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
|
||||
datr->lora.bandwidth = 250.0;
|
||||
break;
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
|
||||
datr->lora.bandwidth = 500.0;
|
||||
break;
|
||||
default:
|
||||
datr->lora.bandwidth = 125.0;
|
||||
}
|
||||
|
||||
datr->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
|
||||
// find the span based on the channel ID
|
||||
uint8_t span = 0;
|
||||
|
@ -401,58 +733,19 @@ int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
|
|||
}
|
||||
|
||||
if(!found) {
|
||||
return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS);
|
||||
return(RADIOLIB_ERR_INVALID_CHANNEL);
|
||||
}
|
||||
|
||||
this->chIndex = chan;
|
||||
RADIOLIB_DEBUG_PRINTLN("Channel span %d, channel %d", span, spanChannelId);
|
||||
|
||||
// set the frequency
|
||||
float freq = this->band->defaultChannels[span].freqStart + this->band->defaultChannels[span].freqStep * (float)spanChannelId;
|
||||
RADIOLIB_DEBUG_PRINTLN("Frequency %f MHz", freq);
|
||||
int state = this->phyLayer->setFrequency(freq);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set the data rate
|
||||
uint8_t dataRateBand = this->band->defaultChannels[span].dataRates[dr];
|
||||
this->dataRate = dr;
|
||||
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||
// find the first usable data rate
|
||||
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
|
||||
if(this->band->defaultChannels[span].dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
|
||||
dataRateBand = this->band->defaultChannels[span].dataRates[i];
|
||||
this->dataRate = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RADIOLIB_DEBUG_PRINTLN("Data rate %02x", dataRateBand);
|
||||
DataRate_t datr;
|
||||
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
|
||||
datr.fsk.bitRate = 50;
|
||||
datr.fsk.freqDev = 25;
|
||||
|
||||
} else {
|
||||
uint8_t bw = dataRateBand & 0x03;
|
||||
switch(bw) {
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
|
||||
datr.lora.bandwidth = 125.0;
|
||||
break;
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
|
||||
datr.lora.bandwidth = 250.0;
|
||||
break;
|
||||
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
|
||||
datr.lora.bandwidth = 500.0;
|
||||
break;
|
||||
default:
|
||||
return(RADIOLIB_ERR_INVALID_BANDWIDTH);
|
||||
}
|
||||
|
||||
datr.lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
|
||||
|
||||
}
|
||||
|
||||
findDataRate(dr, &datr, &this->band->defaultChannels[span]);
|
||||
state = this->phyLayer->setDataRate(datr);
|
||||
|
||||
return(state);
|
||||
|
@ -480,7 +773,6 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
|
|||
// calculate the expected value and compare
|
||||
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
|
||||
if(micCalculated != micReceived) {
|
||||
RADIOLIB_DEBUG_PRINTLN("MIC mismatch, expected 0x%08x, got 0x%08x", micCalculated, micReceived);
|
||||
return(false);
|
||||
}
|
||||
|
||||
|
@ -536,6 +828,113 @@ int16_t LoRaWANNode::setPhyProperties() {
|
|||
return(state);
|
||||
}
|
||||
|
||||
int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen) {
|
||||
// build the command
|
||||
size_t macReqLen = 1 + payloadLen;
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
uint8_t* macReqBuff = new uint8_t[macReqLen];
|
||||
#else
|
||||
uint8_t macReqBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
macReqBuff[0] = cid;
|
||||
memcpy(&macReqBuff[1], payload, payloadLen);
|
||||
|
||||
// uplink it
|
||||
int16_t state = this->uplink(macReqBuff, macReqLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] macReqBuff;
|
||||
#endif
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// build the reply buffer
|
||||
size_t macRplLen = 1 + replyLen;
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
uint8_t* macRplBuff = new uint8_t[this->band->payloadLenMax[this->dataRate]];
|
||||
#else
|
||||
uint8_t macRplBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
|
||||
// wait for reply from the server
|
||||
size_t rxRplLen = 0;
|
||||
state = this->downlink(macRplBuff, &rxRplLen);
|
||||
if(state != RADIOLIB_ERR_NONE) {
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] macRplBuff;
|
||||
#endif
|
||||
return(state);
|
||||
}
|
||||
|
||||
//Module::hexdump(macRplBuff, rxRplLen);
|
||||
|
||||
// check the length - it may be longer than expected
|
||||
// if the server decided to append more MAC commands, but never shorter
|
||||
// TODO how to handle the additional command(s)?
|
||||
if(rxRplLen < macRplLen) {
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] macRplBuff;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
|
||||
}
|
||||
|
||||
// check the CID
|
||||
if(macRplBuff[0] != cid) {
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] macRplBuff;
|
||||
#endif
|
||||
return(RADIOLIB_ERR_INVALID_CID);
|
||||
}
|
||||
|
||||
// copy the data
|
||||
memcpy(reply, &macRplBuff[1], replyLen);
|
||||
#if !defined(RADIOLIB_STATIC_ONLY)
|
||||
delete[] macRplBuff;
|
||||
#endif
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
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;
|
||||
if(len % RADIOLIB_AES128_BLOCK_SIZE) {
|
||||
numBlocks++;
|
||||
}
|
||||
|
||||
// generate the encryption blocks
|
||||
uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
|
||||
encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC;
|
||||
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS] = ctrId;
|
||||
encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = dir;
|
||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
|
||||
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
|
||||
|
||||
//Module::hexdump(uplinkMsg, uplinkMsgLen);
|
||||
|
||||
// now encrypt the input
|
||||
// on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext
|
||||
size_t remLen = len;
|
||||
for(size_t i = 0; i < numBlocks; i++) {
|
||||
if(counter) {
|
||||
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
|
||||
}
|
||||
|
||||
// encrypt the buffer
|
||||
RadioLibAES128Instance.init(key);
|
||||
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
|
||||
|
||||
// now xor the buffer with the input
|
||||
size_t xorLen = remLen;
|
||||
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
|
||||
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
|
||||
}
|
||||
for(uint8_t j = 0; j < xorLen; j++) {
|
||||
out[i*RADIOLIB_AES128_BLOCK_SIZE + j] = in[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
|
||||
}
|
||||
remLen -= xorLen;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
|
||||
uint8_t* buffPtr = buff;
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
// recommended default settings
|
||||
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
|
||||
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS ((RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS) + 1000)
|
||||
#define RADIOLIB_LORAWAN_RX_WINDOW_LEN_MS (500)
|
||||
#define RADIOLIB_LORAWAN_RX1_DR_OFFSET (0)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
|
||||
|
@ -83,14 +84,20 @@
|
|||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS (17)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE (0xFF)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_0 (0x00)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_1 (0x01)
|
||||
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_2 (0x02)
|
||||
|
||||
// join accept message layout
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS (4)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS (12)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1)
|
||||
|
@ -102,17 +109,20 @@
|
|||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY (0x02)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY (0x03)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_ENC_KEY (0x05)
|
||||
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY (0x06)
|
||||
|
||||
// uplink message layout
|
||||
#define RADIOLIB_LORAWAN_UPLINK_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
|
||||
#define RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS (16)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 1)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_FCTRL_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 5)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_FCNT_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 6)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 16)
|
||||
#define RADIOLIB_LORAWAN_UPLINK_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8 + (FOPTS))
|
||||
#define RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 9 + (FOPTS))
|
||||
// frame header layout
|
||||
#define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16)
|
||||
#define RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 1)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FCTRL_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 5)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 16)
|
||||
#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (FOPTS))
|
||||
#define RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 9 + (FOPTS))
|
||||
#define RADIOLIB_LORAWAN_FRAME_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
|
||||
|
||||
// payload encryption/MIC blocks common layout
|
||||
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
|
||||
|
@ -122,6 +132,7 @@
|
|||
|
||||
// payload encryption block layout
|
||||
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
|
||||
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS (4)
|
||||
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
|
||||
|
||||
// payload MIC blocks layout
|
||||
|
@ -133,6 +144,22 @@
|
|||
// magic word saved in persistent memory upon activation
|
||||
#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)
|
||||
|
||||
/*!
|
||||
\struct LoRaWANChannelSpan_t
|
||||
\brief Structure to save information about LoRaWAN channels.
|
||||
|
@ -208,6 +235,12 @@ extern const LoRaWANBand_t AS923;
|
|||
extern const LoRaWANBand_t KR920;
|
||||
extern const LoRaWANBand_t IN865;
|
||||
|
||||
struct LoRaWANMacCommand_t {
|
||||
uint8_t cid;
|
||||
size_t len;
|
||||
uint8_t* payload;
|
||||
};
|
||||
|
||||
/*!
|
||||
\class LoRaWANNode
|
||||
\brief LoRaWAN-compatible node (class A device).
|
||||
|
@ -239,14 +272,14 @@ class LoRaWANNode {
|
|||
/*!
|
||||
\brief Join network by performing over-the-air activation. By this procedure,
|
||||
the device will perform an exchange with the network server and set all necessary configuration.
|
||||
\param appEUI 8-byte application identifier.
|
||||
\param joinEUI 8-byte application identifier.
|
||||
\param devEUI 8-byte device identifier.
|
||||
\param nwkKey Pointer to the network AES-128 key.
|
||||
\param appKey Pointer to the application AES-128 key.
|
||||
\param force Set to true to force joining even if previously joined.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
|
||||
int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
|
||||
|
||||
/*!
|
||||
\brief Join network by performing activation by personalization.
|
||||
|
@ -287,6 +320,60 @@ class LoRaWANNode {
|
|||
*/
|
||||
int16_t uplink(uint8_t* data, size_t len, uint8_t port);
|
||||
|
||||
#if defined(RADIOLIB_BUILD_ARDUINO)
|
||||
/*!
|
||||
\brief Wait for downlink from the server in either RX1 or RX2 window.
|
||||
\param str Address of Arduino String to save the received data.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t downlink(String& str);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\brief Wait for downlink from the server in either RX1 or RX2 window.
|
||||
\param data Buffer to save received data into.
|
||||
\param len Pointer to variable that will be used to save the number of received bytes.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t downlink(uint8_t* data, size_t* len);
|
||||
|
||||
#if !defined(RADIOLIB_GODMODE)
|
||||
private:
|
||||
#endif
|
||||
PhysicalLayer* phyLayer = NULL;
|
||||
const LoRaWANBand_t* band = NULL;
|
||||
|
||||
LoRaWANMacCommand_t* command = NULL;
|
||||
|
||||
// the following is either provided by the network server (OTAA)
|
||||
// or directly entered by the user (ABP)
|
||||
uint32_t devAddr = 0;
|
||||
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||
uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
|
||||
float availableChannelsFreq[5] = { 0 };
|
||||
uint16_t availableChannelsMask[6] = { 0 };
|
||||
|
||||
// LoRaWAN revision (1.0 vs 1.1)
|
||||
uint8_t rev = 0;
|
||||
|
||||
// currently configured data rate DR0 - DR15 (band-dependent!)
|
||||
uint8_t dataRate = 0;
|
||||
|
||||
// currently configured channel (band-dependent!)
|
||||
uint8_t chIndex = 0;
|
||||
|
||||
// timestamp to measure the RX1/2 delay (from uplink end)
|
||||
uint32_t rxDelayStart = 0;
|
||||
|
||||
// delays between the uplink and RX1/2 windows
|
||||
uint32_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS };
|
||||
|
||||
// find the first usable data rate in a given channel span
|
||||
void findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span);
|
||||
|
||||
/*!
|
||||
\brief Configure the radio to a given channel frequency and data rate.
|
||||
\param chan Channel ID to set.
|
||||
|
@ -295,32 +382,6 @@ class LoRaWANNode {
|
|||
*/
|
||||
int16_t configureChannel(uint8_t chan, uint8_t dr);
|
||||
|
||||
#if !defined(RADIOLIB_GODMODE)
|
||||
private:
|
||||
#endif
|
||||
PhysicalLayer* phyLayer;
|
||||
const LoRaWANBand_t* band;
|
||||
|
||||
// the following is either provided by the network server (OTAA)
|
||||
// or directly entered by the user (ABP)
|
||||
uint32_t devAddr;
|
||||
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE];
|
||||
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
|
||||
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
|
||||
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE];
|
||||
uint8_t rxDelay;
|
||||
float availableChannelsFreq[5];
|
||||
uint16_t availableChannelsMask[6];
|
||||
|
||||
// LoRaWAN revision (1.0 vs 1.1)
|
||||
uint8_t rev;
|
||||
|
||||
// currently configured data rate DR0 - DR15 (band-dependent!)
|
||||
uint8_t dataRate;
|
||||
|
||||
// currently configured channel (band-dependent!)
|
||||
uint8_t chIndex;
|
||||
|
||||
// method to generate message integrity code
|
||||
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
|
||||
|
||||
|
@ -328,8 +389,15 @@ class LoRaWANNode {
|
|||
// it assumes that the MIC is the last 4 bytes of the message
|
||||
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
|
||||
|
||||
// configure the physical layer properties (frequency, sync word etc.)
|
||||
int16_t setPhyProperties();
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// network-to-host conversion method - takes data from network packet and converts it to the host endians
|
||||
template<typename T>
|
||||
static T ntoh(uint8_t* buff, size_t size = 0);
|
||||
|
|
Loading…
Add table
Reference in a new issue