[LoRaWAN] Added basic LoRaWAN support (#58)

This commit is contained in:
jgromes 2023-07-06 13:41:31 +02:00
parent 3012185af4
commit 0d2ef419bf
7 changed files with 1146 additions and 5 deletions

View file

@ -42,6 +42,8 @@ SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x
SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x
* [__POCSAG__](https://www.sigidwiki.com/wiki/POCSAG) using 2-FSK for modules:
SX127x, RFM9x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x
* [__LoRaWAN__](https://lora-alliance.org/) using LoRa for modules:
SX127x, RFM9x, SX126x and SX128x
### Supported Arduino platforms:
* __Arduino__
@ -87,11 +89,6 @@ SX127x, RFM9x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x
The list above is by no means exhaustive - RadioLib code is independent of the used platform! Compilation of all examples is tested for all platforms officially supported prior to releasing new version. In addition, RadioLib includes an internal hardware abstraction layer, which allows it to be easily ported even to non-Arduino environments.
### In development:
* __AX5243__ FSK module
* __LoRaWAN__ protocol for SX127x, RFM9x and SX126x modules
* ___and more!___
## Frequently Asked Questions
### Where should I start?

View file

@ -0,0 +1,124 @@
/*
RadioLib LoRaWAN End Device Example
This example joins a LoRaWAN network and will send
uplink packets. Before you start, you will have to
register your device at https://www.thethingsnetwork.org/
After your device is registered, you can run this example.
The device will join the network and start uploading data.
NOTE: LoRaWAN requires storing some parameters persistently!
RadioLib does this by using EEPROM, by default
starting at address 0 and using 32 bytes.
If you already use EEPROM in your application,
you will have to either avoid this range, or change it
by setting a different start address by changing the value of
RADIOLIB_HAL_PERSISTENT_STORAGE_BASE macro, either
during build or in src/BuildOpt.h.
For default module settings, see the wiki page
https://github.com/jgromes/RadioLib/wiki/Default-configuration
For full API reference, see the GitHub Pages
https://jgromes.github.io/RadioLib/
*/
// include the library
#include <RadioLib.h>
// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
SX1278 radio = new Module(10, 2, 9, 3);
// create the node instance on the EU-868 band
// using the radio module and the encryption key
// make sure you are using the correct band
// based on your geographical location!
LoRaWANNode node(&radio, &EU868);
void setup() {
Serial.begin(9600);
// initialize SX1278 with default settings
Serial.print(F("[SX1278] Initializing ... "));
int state = radio.begin();
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
while(true);
}
// first we need to initialize the device storage
// this will reset all persistently stored parameters
// NOTE: This should only be done once prior to first joining a network!
// After wiping persistent storage, you will also have to reset
// the end device in TTN and perform the join procedure again!
//node.wipe();
// application identifier - in LoRaWAN 1.1, it is also called joinEUI
// 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;
// device identifier - this number can be anything
// when adding new end device in TTN, you can generate this number,
// or you can set any value you want, provided it is also unique
uint64_t devEUI = 0x70B3D57ED005E120;
// select some encryption keys which will be used to secure the communication
// there are two of them - network key and application key
// because LoRaWAN uses AES-128, the key MUST be 16 bytes (or characters) long
const char nwkKey[] = "topSecretKey1234";
const char appKey[] = "aDifferentKeyABC";
// 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);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
while(true);
}
// after the device has been activated,
// network can be rejoined after device power cycle
// by calling "begin"
/*
Serial.print(F("[LoRaWAN] Resuming previous session ... "));
state = node.begin();
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
while(true);
}
*/
}
// counter to keep track of transmitted packets
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);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
}
// wait before sending another one
delay(10000);
}

View file

@ -56,6 +56,8 @@ APRSClient KEYWORD1
PagerClient KEYWORD1
ExternalRadio KEYWORD1
BellClient KEYWORD1
LoRaWANNode KEYWORD1
LoRaWANBand_t KEYWORD1
# SSTV modes
Scottie1 KEYWORD1
@ -269,10 +271,18 @@ setPacketReceivedAction KEYWORD2
clearPacketReceivedAction KEYWORD2
setPacketSentAction KEYWORD2
clearPacketSentAction KEYWORD2
setDataRate KEYWORD2
# BellModem
setModem KEYWORD2
# LoRaWAN
wipe KEYWORD2
beginOTAA KEYWORD2
beginAPB KEYWORD2
uplink KEYWORD2
configureChannel KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View file

@ -109,6 +109,7 @@
#include "protocols/ExternalRadio/ExternalRadio.h"
#include "protocols/Print/Print.h"
#include "protocols/BellModem/BellModem.h"
#include "protocols/LoRaWAN/LoRaWAN.h"
// utilities
#include "utils/CRC.h"

View file

@ -0,0 +1,494 @@
#include "LoRaWAN.h"
#include <string.h>
#if !defined(RADIOLIB_EXCLUDE_LORAWAN)
// flag to indicate whether we have received a downlink
static volatile bool downlinkReceived = false;
// interrupt service routine to handle downlinks automatically
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
static void LoRaWANNodeOnDownlink(void) {
downlinkReceived = true;
}
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
this->phyLayer = phy;
this->band = band;
}
void LoRaWANNode::wipe() {
Module* mod = this->phyLayer->getMod();
mod->hal->wipePersistentStorage();
}
int16_t LoRaWANNode::begin() {
int16_t state = this->setPhyProperties();
RADIOLIB_ASSERT(state);
// check the magic value
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);
}
// 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);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, 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)) {
// the device has joined already, we can just pull the data from persistent storage
return(this->begin());
}
// set the physical layer configuration
int16_t state = this->setPhyProperties();
RADIOLIB_ASSERT(state);
// 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_DEV_EUI_POS], devEUI);
LoRaWANNode::hton<uint16_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce);
// add the authentication code
uint32_t mic = this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), nwkKey);
LoRaWANNode::hton<uint32_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic);
// send it
state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);
RADIOLIB_ASSERT(state);
// set the function that will be called when the reply is received
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
// downlink messages are sent with interted IQ
state = this->phyLayer->invertIQ(true);
RADIOLIB_ASSERT(state);
// start receiving
uint32_t start = mod->hal->millis();
state = this->phyLayer->startReceive();
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;
this->phyLayer->invertIQ(false);
return(RADIOLIB_ERR_RX_TIMEOUT);
}
}
// we have a message, reset the IQ inversion
downlinkReceived = false;
this->phyLayer->clearPacketReceivedAction();
state = this->phyLayer->invertIQ(false);
RADIOLIB_ASSERT(state);
// build the buffer for the reply data
uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
// check received length
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);
}
// read the packet
state = this->phyLayer->readData(joinAcceptMsgEnc, lenRx);
RADIOLIB_ASSERT(state);
// 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);
}
// 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
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);
// 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];
// process CFlist if present
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) {
// list of frequencies
for(uint8_t i = 0; i < 5; i++) {
uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + 3*i], 3);
availableChannelsFreq[i] = (float)freq/10000.0;
RADIOLIB_DEBUG_PRINTLN("Channel %d frequency = %f MHz", i, availableChannelsFreq[i]);
}
} else {
// TODO list of masks
RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)");
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
}
}
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
RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)");
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
} else {
// 1.0 version, just derive the keys
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
memcpy(this->sNwkSIntKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->nwkSEncKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
}
// save the device address
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr);
// update the keys
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
// all complete, reset device counters and set the magic number
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, 0);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC);
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::beginAPB(uint32_t addr, uint8_t net, uint8_t* nwkSKey, uint8_t* appSKey) {
this->devAddr = (((uint32_t)net << 25) & 0xFE000000) | (addr & 0x01FFFFFF);
memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE);
return(RADIOLIB_ERR_NONE);
}
#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t LoRaWANNode::uplink(String& str, uint8_t port) {
return(this->uplink(str.c_str(), port));
}
#endif
int16_t LoRaWANNode::uplink(const char* str, uint8_t port) {
return(this->uplink((uint8_t*)str, strlen(str), 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);
// check maximum payload len as defiend in phy
// TODO implement Fopts
uint8_t foptsLen = 0;
if(len > this->band->payloadLenMax[this->dataRate]) {
return(RADIOLIB_ERR_PACKET_TOO_LONG);
}
// build the uplink message
// the first 16 bytes are reserved for MIC calculation blocks
size_t uplinkMsgLen = RADIOLIB_LORAWAN_UPLINK_LEN(len, foptsLen);
#if defined(RADIOLIB_STATIC_ONLY)
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen];
#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);
// TODO implement adaptive data rate
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCTRL_POS] = 0x00;
// 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);
// TODO implement FOpts
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port;
// select encryption key based on the target port
uint8_t* encKey = this->appSKey;
if(port == 0) {
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;
}
// create blocks for MIC calculation
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC;
block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK;
LoRaWANNode::hton<uint32_t>(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
LoRaWANNode::hton<uint32_t>(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t);
uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE);
// TODO implement confirmed frames
block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRate;
block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->chIndex;
//Module::hexdump(uplinkMsg, uplinkMsgLen);
// calculate authentication codes
memcpy(uplinkMsg, block1, RADIOLIB_AES128_BLOCK_SIZE);
uint32_t micS = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->sNwkSIntKey);
memcpy(uplinkMsg, block0, RADIOLIB_AES128_BLOCK_SIZE);
uint32_t micF = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->fNwkSIntKey);
// TODO check LoRaWAN 1.0/1.1
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF);
//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);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] uplinkMsg;
#endif
RADIOLIB_ASSERT(state);
// TODO implement listening for downlinks in RX1/RX2 slots
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
// find the span based on the channel ID
uint8_t span = 0;
uint8_t spanChannelId = 0;
bool found = false;
for(uint8_t chanCtr = 0; span < this->band->numChannelSpans; span++) {
for(; spanChannelId < this->band->defaultChannels[span].numChannels; spanChannelId++) {
if(chanCtr >= chan) {
found = true;
break;
}
chanCtr++;
}
if(found) {
break;
}
}
if(!found) {
return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS);
}
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;
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 & 0xF0) >> 4) + 6;
state = this->phyLayer->setDataRate(datr);
return(state);
}
uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) {
if((msg == NULL) || (len == 0)) {
return(0);
}
RadioLibAES128Instance.init(key);
uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE];
RadioLibAES128Instance.generateCMAC(msg, len, cmac);
return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24);
}
bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
if((msg == NULL) || (len < sizeof(uint32_t))) {
return(0);
}
// extract MIC from the message
uint32_t micReceived = LoRaWANNode::ntoh<uint32_t>(&msg[len - sizeof(uint32_t)]);
// 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);
}
return(true);
}
int16_t LoRaWANNode::setPhyProperties() {
// set the physical layer configuration
// TODO select channel span based on channel ID
// TODO select channel randomly
uint8_t channelId = 0;
int16_t state = this->configureChannel(channelId, this->band->defaultChannels[0].joinRequestDataRate);
RADIOLIB_ASSERT(state);
state = this->phyLayer->setOutputPower(this->band->powerMax);
RADIOLIB_ASSERT(state);
uint8_t syncWord = RADIOLIB_LORAWAN_LORA_SYNC_WORD;
state = this->phyLayer->setSyncWord(&syncWord, 1);
RADIOLIB_ASSERT(state);
state = this->phyLayer->setPreambleLength(RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN);
return(state);
}
template<typename T>
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
uint8_t* buffPtr = buff;
size_t targetSize = sizeof(T);
if(size != 0) {
targetSize = size;
}
T res = 0;
for(uint8_t i = 0; i < targetSize; i++) {
res |= (uint32_t)(*(buffPtr++)) << 8*i;
}
return(res);
}
template<typename T>
void LoRaWANNode::hton(uint8_t* buff, T val, size_t size) {
uint8_t* buffPtr = buff;
size_t targetSize = sizeof(T);
if(size != 0) {
targetSize = size;
}
for(uint8_t i = 0; i < targetSize; i++) {
*(buffPtr++) = val >> 8*i;
}
}
#endif

View file

@ -0,0 +1,324 @@
#if !defined(_RADIOLIB_LORAWAN_H) && !defined(RADIOLIB_EXCLUDE_LORAWAN)
#define _RADIOLIB_LORAWAN_H
#include "../../TypeDef.h"
#include "../PhysicalLayer/PhysicalLayer.h"
#include "../../utils/Cryptography.h"
// preamble format
#define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34)
#define RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN (8)
#define RADIOLIB_LORAWAN_GFSK_SYNC_WORD (0xC194C1)
#define RADIOLIB_LORAWAN_GFSK_PREAMBLE_LEN (5)
// MAC header field encoding MSB LSB DESCRIPTION
#define RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST (0x00 << 5) // 7 5 message type: join request
#define RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT (0x01 << 5) // 7 5 join accept
#define RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP (0x02 << 5) // 7 5 unconfirmed data up
#define RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_DOWN (0x03 << 5) // 7 5 unconfirmed data down
#define RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP (0x04 << 5) // 7 5 confirmed data up
#define RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN (0x05 << 5) // 7 5 confirmed data down
#define RADIOLIB_LORAWAN_MHDR_MTYPE_PROPRIETARY (0x07 << 5) // 7 5 proprietary
#define RADIOLIB_LORAWAN_MHDR_MTYPE_MASK (0x07 << 5) // 7 5 bitmask of all possible options
#define RADIOLIB_LORAWAN_MHDR_MAJOR_R1 (0x00 << 0) // 1 0 major version: LoRaWAN R1
// frame control field encoding
#define RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED (0x01 << 7) // 7 7 adaptive data rate: enabled
#define RADIOLIB_LORAWAN_FCTRL_ADR_DISABLED (0x00 << 7) // 7 7 disabled
#define RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ (0x01 << 6) // 6 6 adaptive data rate ACK request
#define RADIOLIB_LORAWAN_FCTRL_ACK (0x01 << 5) // 5 5 confirmed message acknowledge
#define RADIOLIB_LORAWAN_FCTRL_FRAME_PENDING (0x01 << 4) // 4 4 downlink frame is pending
// port field
#define RADIOLIB_LORAWAN_FPORT_MAC_COMMAND (0x00 << 0) // 7 0 payload contains MAC commands only
#define RADIOLIB_LORAWAN_FPORT_RESERVED (0xE0 << 0) // 7 0 reserved port values
// MAC commands - only those sent from end-device to gateway
#define RADIOLIB_LORAWAN_LINK_CHECK_REQ (0x02 << 0) // 7 0 MAC command: request to check connectivity to network
#define RADIOLIB_LORAWAN_LINK_ADR_ANS (0x03 << 0) // 7 0 answer to ADR change
#define RADIOLIB_LORAWAN_DUTY_CYCLE_ANS (0x04 << 0) // 7 0 answer to duty cycle change
#define RADIOLIB_LORAWAN_RX_PARAM_SETUP_ANS (0x05 << 0) // 7 0 answer to reception slot setup request
#define RADIOLIB_LORAWAN_DEV_STATUS_ANS (0x06 << 0) // 7 0 device status information
#define RADIOLIB_LORAWAN_NEW_CHANNEL_ANS (0x07 << 0) // 7 0 acknowledges change of a radio channel
#define RADIOLIB_LORAWAN_RX_TIMING_SETUP_ANS (0x08 << 0) // 7 0 acknowledges change of a reception slots timing
#define RADIOLIB_LORAWAN_NOPTS_LEN (8)
// data rat encoding
#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x06 << 4) // 7 4 LoRaWAN spreading factor: SF12
#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x05 << 4) // 7 4 SF11
#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x04 << 4) // 7 4 SF10
#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x03 << 4) // 7 4 SF9
#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x02 << 4) // 7 4 SF8
#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x01 << 4) // 7 4 SF7
#define RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K (0x00 << 4) // 7 4 FSK @ 50 kbps
#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x00 << 0) // 3 0 LoRaWAN bandwidth: 500 kHz
#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 0) // 3 0 250 kHz
#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x02 << 0) // 3 0 125 kHz
#define RADIOLIB_LORAWAN_DATA_RATE_UNUSED (0xFF << 0) // 7 0 unused data rate
#define RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK (0x00 << 0)
#define RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK (0x01 << 0)
#define RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH (0x02 << 0)
#define RADIOLIB_LORAWAN_CHANNEL_DIR_NONE (0x03 << 0)
#define RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES (0)
#define RADIOLIB_LORAWAN_CFLIST_TYPE_MASK (1)
#define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (16)
// 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_RX1_DR_OFFSET (0)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
#define RADIOLIB_LORAWAN_MAX_FCNT_GAP (16384)
#define RADIOLIB_LORAWAN_ADR_ACK_LIMIT (64)
#define RADIOLIB_LORAWAN_ADR_ACK_DELAY (32)
#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MIN_MS (1000)
#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MAX_MS (3000)
#define RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM (-2)
// join request message layout
#define RADIOLIB_LORAWAN_JOIN_REQUEST_LEN (23)
#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)
// 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_DL_SETTINGS_POS (11)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_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)
// join accept message variables
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_0 (0x00 << 7) // 7 7 LoRaWAN revision: 1.0
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1 (0x01 << 7) // 7 7 1.1
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY (0x01)
#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)
// 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))
// payload encryption/MIC blocks common layout
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
#define RADIOLIB_LORAWAN_BLOCK_DIR_POS (5)
#define RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS (6)
#define RADIOLIB_LORAWAN_BLOCK_FCNT_POS (10)
// payload encryption block layout
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
// payload MIC blocks layout
#define RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC (0x49)
#define RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS (15)
#define RADIOLIB_LORAWAN_MIC_DATA_RATE_POS (3)
#define RADIOLIB_LORAWAN_MIC_CH_INDEX_POS (4)
#define RADIOLIB_LORAWAN_MAGIC (0x12AD101B)
/*!
\struct LoRaWANChannelSpan_t
\brief Structure to save information about LoRaWAN channels.
To save space, adjacent channels are saved in "spans".
*/
struct LoRaWANChannelSpan_t {
/*! \brief Whether this channel span is for uplink, downlink, or both directions*/
uint8_t direction;
/*! \brief Allowed data rates for a join request message */
uint8_t joinRequestDataRate;
/*! \brief Total number of channels in the span */
uint8_t numChannels;
/*! \brief Center frequency of the first channel in span */
float freqStart;
/*! \brief Frequency step between adjacent channels */
float freqStep;
/*! \brief Array of datarates supported by all channels in the span */
uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES];
};
// alias for unused channel span
#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .direction = RADIOLIB_LORAWAN_CHANNEL_DIR_NONE, .joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, .numChannels = 0, .freqStart = 0, .freqStep = 0, .dataRates = { 0 } }
/*!
\struct LoRaWANBand_t
\brief Structure to save information about LoRaWAN band
*/
struct LoRaWANBand_t {
/*! \brief The base downlik data rate. Used to calculate data rate changes for adaptive data rate */
uint8_t downlinkDataRateBase;
/*! \brief The minimum allowed downlik data rate. Used to calculate data rate changes for adaptive data rate */
uint8_t downlinkDataRateMin;
/*! \brief Array of allowed maximum payload lengths for each data rate */
uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES];
/*! \brief Maximum allowed output power in this band in dBm */
int8_t powerMax;
/*! \brief Number of power steps in this band */
int8_t powerNumSteps;
/*! \brief Whether the optional channels are defined as list of frequencies or bit mask */
uint8_t cfListType;
/*! \brief Number of channel spans in the band */
uint8_t numChannelSpans;
/*! \brief Default uplink (TX/RX1) channels defined by LoRaWAN Regional Parameters */
LoRaWANChannelSpan_t defaultChannels[3];
/*! \brief Backup downlink (RX2) channel - just a single channel, but using the same structure for convenience */
LoRaWANChannelSpan_t backupChannel;
};
// supported bands
extern const LoRaWANBand_t EU868;
extern const LoRaWANBand_t US915;
/*!
\class LoRaWANNode
\brief LoRaWAN-compatible node (class A device).
*/
class LoRaWANNode {
public:
/*!
\brief Default constructor.
\param phy Pointer to the PhysicalLayer radio module.
\param band Pointer to the LoRaWAN band to use.
*/
LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band);
/*!
\brief Wipe internal persistent parameters.
This will reset all counters and saved variables, so the device will have to rejoin the network.
*/
void wipe();
/*!
\brief Join network by loading information from persistent storage.
\returns \ref status_codes
*/
int16_t begin();
/*!
\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 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);
/*!
\brief Join network by performing activation by personalization.
In this procedure, all necessary configuration must be provided by the user.
\param addr Device address.
\param net Network ID.
\param nwkSKey Pointer to the network session AES-128 key.
\param appSKey Pointer to the application session AES-128 key.
\returns \ref status_codes
*/
int16_t beginAPB(uint32_t addr, uint8_t net, uint8_t* nwkSKey, uint8_t* appSKey);
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
\brief Send a message to the server.
\param str Address of Arduino String that will be transmitted.
\param port Port number to send the message to.
\returns \ref status_codes
*/
int16_t uplink(String& str, uint8_t port);
#endif
/*!
\brief Send a message to the server.
\param str C-string that will be transmitted.
\param port Port number to send the message to.
\returns \ref status_codes
*/
int16_t uplink(const char* str, uint8_t port);
/*!
\brief Send a message to the server.
\param data Data to send.
\param len Length of the data.
\param port Port number to send the message to.
\returns \ref status_codes
*/
int16_t uplink(uint8_t* data, size_t len, uint8_t port);
/*!
\brief Configure the radio to a given channel frequency and data rate.
\param chan Channel ID to set.
\param dr Data rate to set, DR0 - DR15.
\returns \ref status_codes
*/
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];
// 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);
// method to verify message integrity code
// it assumes that the MIC is the last 4 bytes of the message
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
int16_t setPhyProperties();
// 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);
// host-to-network conversion method - takes data from host variable and and converts it to network packet endiands
template<typename T>
static void hton(uint8_t* buff, T val, size_t size = 0);
};
#endif

View file

@ -0,0 +1,191 @@
#include "LoRaWAN.h"
#if !defined(RADIOLIB_EXCLUDE_LORAWAN)
uint8_t getDownlinkDataRate(uint8_t uplink, uint8_t offset, uint8_t base, uint8_t lim) {
int8_t dr = uplink - offset + base;
if(dr < lim) {
dr = lim;
}
return(dr);
}
const LoRaWANBand_t EU868 = {
.downlinkDataRateBase = 0,
.downlinkDataRateMin = 0,
.payloadLenMax = {
59, 59, 59, 123, 230, 230, 230, 230,
0, 0, 0, 0, 0, 0, 0, 0 },
.powerMax = 16,
.powerNumSteps = 7,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES,
.numChannelSpans = 1,
.defaultChannels = {
{
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_BOTH,
.joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
.numChannels = 3,
.freqStart = 868.1,
.freqStep = 0.2,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED
}
},
RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE,
RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE,
},
.backupChannel = {
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK,
.joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
.numChannels = 1,
.freqStart = 869.858,
.freqStep = 0,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
}
}
};
const LoRaWANBand_t US915 = {
.downlinkDataRateBase = 10,
.downlinkDataRateMin = 8,
.payloadLenMax = {
19, 61, 133, 250, 250, 0, 0, 0,
41, 117, 230, 230, 230, 230, 0, 0 },
.powerMax = 30,
.powerNumSteps = 10,
.cfListType = RADIOLIB_LORAWAN_CFLIST_TYPE_MASK,
.numChannelSpans = 3,
.defaultChannels = {
{
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK,
.joinRequestDataRate = 0,
.numChannels = 64,
.freqStart = 902.3,
.freqStep = 0.2,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
}
}, {
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK,
.joinRequestDataRate = 4,
.numChannels = 8,
.freqStart = 903,
.freqStep = 1.6,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
}
}, {
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK,
.joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
.numChannels = 8,
.freqStart = 923.3,
.freqStep = 0.6,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
}
},
},
.backupChannel = {
.direction = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK,
.joinRequestDataRate = RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
.numChannels = 1,
.freqStart = 869.858,
.freqStep = 0,
.dataRates = {
RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
RADIOLIB_LORAWAN_DATA_RATE_UNUSED,
}
}
};
#endif