Tasmota/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino

630 lines
29 KiB
C++

/*
xdrv_73_8_lorawan_bridge.ino - LoRaWan EU868 support for Tasmota
SPDX-FileCopyrightText: 2024 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifdef USE_SPI_LORA
#ifdef USE_LORAWAN_BRIDGE
/*********************************************************************************************\
* EU868 LoRaWan bridge support
*
* The goal of the LoRaWan Bridge is to receive from joined LoRaWan End-Devices or Nodes and
* provide decrypted MQTT data.
*
* EU868 LoRaWan uses at minimum alternating 3 frequencies and 6 spreadingfactors (datarate or DR)
* which makes the use of single fixed frequency and spreadingfactor hardware like
* SX127x (LiliGo T3, M5 LoRa868 or RFM95W) or SX126x (LiLiGo T3S3) a challenge.
* This driver uses one fixed frequency and spreadingfactor trying to tell the End-Device to do
* the same using Over The Air Activation (OTAA). In some cases the End-Device needs to be
* (serial) configured to use a single channel and fixed datarate.
*
* To be able to do this:
* - Tasmota needs a filesystem to store persistent data (#define USE_UFILESYS)
* - Tasmota Lora has to be configured for one of the three EU868 supported frequencies and
* spreadingfactor using command
* LoRaConfig 2
* or individual commands
* LoRaFrequency 868.1 (or 868.3 and 868.5)
* LoRaSpreadingFactor 9 (or 7..12 equals LoRaWan DataRate 5..0)
* LoRaBandwidth 125
* LoRaCodingRate4 5
* LoRaSyncWord 52
* - LoRaWan has to be enabled (#define USE_LORAWAN_BRIDGE) and configured for the End-Device
* 32 character AppKey using command LoRaWanAppKey <vendor provided appkey>
* - The End-Device needs to start it's LoRaWan join process as documented by vendor.
* This will configure the LoRanWan bridge with the needed information. The bridge will also
* try to configure the device for single frequency and datarate. The process can take
* over 5 minutes as the device transmits at different frequencies and DR.
* - If all's well MQTT data will appear when the End-Device sends it's data.
* - If a device does not support Adaptive Datarate Control (ADR) the device needs to be set to
* a fixed frequency and datarate externally. As an example the Dragino LDS02 needs to be
* configured before joining using it's serial interface with the following commands
* Password 123456
* AT+CHS=868100000
* AT+CADR=0
* AT+CDATARATE=3
*
* The current driver supports decoding for Dragino LDS02 Door/Window sensor and MerryIoT
* DW10 Door/Window sensor. Both battery powered.
*
* The MQTT output can be user defined with SetOption commands like the Zigbee driver
* SetOption89 - Distinct MQTT topics per device (1) (#7835)
* SetOption100 - Remove LwReceived form JSON message (1)
* SetOption118 - Move LwReceived from JSON message and into the subtopic replacing "SENSOR"
* SetOption119 - Remove the device addr from json payload, can be used with LoRaWanName
* where the addr is already known from the topic
* SetOption144 - Include time in `LwReceived` messages like other sensors (SO100 0 and SO118 0)
* LoRaOption3 - Disable driver decoding (1) and provide decrypted payload for decoding
*
* The LoRaWanBridge is default off. Enable it with command
* LoRaWanBridge 1
\*********************************************************************************************/
/*********************************************************************************************\
* Driver Settings load and save
\*********************************************************************************************/
#ifdef USE_UFILESYS
#define D_JSON_APPKEY "AppKey"
#define D_JSON_DEVEUI "DevEUI"
#define D_JSON_DEVNONCE "DevNonce"
#define D_JSON_FCNTUP "FCntUp"
#define D_JSON_FCNTDOWN "FCntDown"
#define D_JSON_FLAGS "Flags"
bool LoraWanLoadData(void) {
char key[12]; // Max 99 nodes (drvset73_1 to drvset73_99)
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
snprintf_P(key, sizeof(key), PSTR(XDRV_73_KEY "_%d"), n +1);
String json = UfsJsonSettingsRead(key);
if (json.length() == 0) { continue; } // Only load used slots
// {"AppKey":"00000000000000000000000000000000","DevEUI","0000000000000000","DevNonce":0,"FCntUp":0,"FCntDown":0,"Flags":0,"NAME":""}
JsonParser parser((char*)json.c_str());
JsonParserObject root = parser.getRootObject();
if (!root) { continue; } // Only load used slots
const char* app_key = nullptr;
app_key = root.getStr(PSTR(D_JSON_APPKEY), nullptr);
if (strlen(app_key)) {
HexToBytes(app_key, Lora->settings.end_node[n].AppKey, TAS_LORAWAN_AES128_KEY_SIZE);
}
Lora->settings.end_node[n].DevEUIh = root.getUInt(PSTR(D_JSON_DEVEUI "h"), Lora->settings.end_node[n].DevEUIh);
Lora->settings.end_node[n].DevEUIl = root.getUInt(PSTR(D_JSON_DEVEUI "l"), Lora->settings.end_node[n].DevEUIl);
Lora->settings.end_node[n].DevNonce = root.getUInt(PSTR(D_JSON_DEVNONCE), Lora->settings.end_node[n].DevNonce);
Lora->settings.end_node[n].FCntUp = root.getUInt(PSTR(D_JSON_FCNTUP), Lora->settings.end_node[n].FCntUp);
Lora->settings.end_node[n].FCntDown = root.getUInt(PSTR(D_JSON_FCNTDOWN), Lora->settings.end_node[n].FCntDown);
Lora->settings.end_node[n].flags = root.getUInt(PSTR(D_JSON_FLAGS), Lora->settings.end_node[n].flags);
const char* name = nullptr;
name = root.getStr(PSTR(D_JSON_NAME), nullptr);
if (strlen(app_key)) {
Lora->settings.end_node[n].name = name;
}
}
return true;
}
bool LoraWanSaveData(void) {
bool result = false;
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
if (Lora->settings.end_node[n].AppKey[0] > 0) { // Only save used slots
Response_P(PSTR("{\"" XDRV_73_KEY "_%d\":{\"" D_JSON_APPKEY "\":\"%16_H\""
",\"" D_JSON_DEVEUI "h\":%lu,\"" D_JSON_DEVEUI "l\":%lu"
",\"" D_JSON_DEVNONCE "\":%u"
",\"" D_JSON_FCNTUP "\":%u,\"" D_JSON_FCNTDOWN "\":%u"
",\"" D_JSON_FLAGS "\":%u"
",\"" D_JSON_NAME "\":\"%s\"}}"),
n +1,
Lora->settings.end_node[n].AppKey,
Lora->settings.end_node[n].DevEUIh, Lora->settings.end_node[n].DevEUIl,
Lora->settings.end_node[n].DevNonce,
Lora->settings.end_node[n].FCntUp, Lora->settings.end_node[n].FCntDown,
Lora->settings.end_node[n].flags,
Lora->settings.end_node[n].name.c_str());
result = UfsJsonSettingsWrite(ResponseData());
}
}
return result;
}
void LoraWanDeleteData(void) {
char key[12]; // Max 99 nodes (drvset73_1 to drvset73_99)
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
snprintf_P(key, sizeof(key), PSTR(XDRV_73_KEY "_%d"), n +1);
UfsJsonSettingsDelete(key); // Use defaults
}
}
#endif // USE_UFILESYS
/*********************************************************************************************/
#include <Ticker.h>
Ticker LoraWan_Send;
void LoraWanTickerSend(void) {
Lora->send_buffer_step--;
if (1 == Lora->send_buffer_step) {
Lora->rx = true; // Always send during RX1
Lora->receive_time = 0; // Reset receive timer
LoraWan_Send.once_ms(TAS_LORAWAN_RECEIVE_DELAY2, LoraWanTickerSend); // Retry after 1000 ms
}
if (Lora->rx) { // If received in RX1 do not resend in RX2
LoraSend(Lora->send_buffer, Lora->send_buffer_len, true);
}
}
void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) {
free(Lora->send_buffer); // Free previous buffer (if any)
Lora->send_buffer = (uint8_t*)malloc(len +1);
if (nullptr == Lora->send_buffer) { return; }
memcpy(Lora->send_buffer, buffer, len);
Lora->send_buffer_len = len;
Lora->send_buffer_step = 2; // Send at RX1 and RX2
LoraWan_Send.once_ms(lorawan_delay - TimePassedSince(Lora->receive_time), LoraWanTickerSend);
}
/*-------------------------------------------------------------------------------------------*/
uint32_t LoraWanSpreadingFactorToDataRate(void) {
// Allow only JoinReq message datarates (125kHz bandwidth)
if (Lora->settings.spreading_factor > 12) {
Lora->settings.spreading_factor = 12;
}
if (Lora->settings.spreading_factor < 7) {
Lora->settings.spreading_factor = 7;
}
Lora->settings.bandwidth = 125;
return 12 - Lora->settings.spreading_factor;
}
uint32_t LoraWanFrequencyToChannel(void) {
// EU863-870 (EU868) JoinReq message frequencies are 868.1, 868.3 and 868.5
uint32_t frequency = (Lora->settings.frequency * 10);
uint32_t channel = 250;
if (8681 == frequency) {
channel = 0;
}
else if (8683 == frequency) {
channel = 1;
}
else if (8685 == frequency) {
channel = 2;
}
if (250 == channel) {
Lora->settings.frequency = 868.1;
Lora->Config();
channel = 0;
}
return channel;
}
/*********************************************************************************************/
void LoraWanSendLinkADRReq(uint32_t node) {
uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++;
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
uint8_t data[17];
data[0] = TAS_LORAWAN_MTYPE_CONFIRMED_DATA_DOWNLINK << 5;
data[1] = DevAddr;
data[2] = DevAddr >> 8;
data[3] = DevAddr >> 16;
data[4] = DevAddr >> 24;
data[5] = 0x05; // FCtrl with 5 FOpts
data[6] = FCnt;
data[7] = FCnt >> 8;
data[8] = TAS_LORAWAN_CID_LINK_ADR_REQ;
data[9] = LoraWanSpreadingFactorToDataRate() << 4 | 0x0F; // DataRate 3 and unchanged TXPower
data[10] = 0x01 << LoraWanFrequencyToChannel(); // Single channel
data[11] = 0x00;
data[12] = 0x00; // ChMaskCntl applies to Channels0..15, NbTrans is default (1)
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, 13);
data[13] = MIC;
data[14] = MIC >> 8;
data[15] = MIC >> 16;
data[16] = MIC >> 24;
// A0 F3F51700 05 0000 033F010000 0B2C1B8B
LoraWanSendResponse(data, sizeof(data), TAS_LORAWAN_RECEIVE_DELAY1);
}
void LoraWanSendMacResponse(uint32_t node, uint8_t* FOpts, uint32_t FCtrl) {
if (FCtrl > 15) { return; } // FOpts = 0..15
uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++;
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
uint32_t data_size = 12 + FCtrl;
uint8_t data[data_size];
data[0] = TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_DOWNLINK << 5;
data[1] = DevAddr;
data[2] = DevAddr >> 8;
data[3] = DevAddr >> 16;
data[4] = DevAddr >> 24;
data[5] = 0x20 + FCtrl; // FCtrl ACK + FOpts
data[6] = FCnt;
data[7] = FCnt >> 8;
for (uint32_t i = 0; i < FCtrl; i++) {
data[8 +i] = FOpts[i];
}
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, data_size -4);
data[data_size -4] = MIC;
data[data_size -3] = MIC >> 8;
data[data_size -2] = MIC >> 16;
data[data_size -1] = MIC >> 24;
LoraWanSendResponse(data, data_size, TAS_LORAWAN_RECEIVE_DELAY1);
}
bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
bool result = false;
uint32_t MType = data[0] >> 5; // Upper three bits (used to be called FType)
if (TAS_LORAWAN_MTYPE_JOIN_REQUEST == MType) {
// 0007010000004140A8D64A89710E4140A82893A8AD137F - Dragino
// 000600000000161600B51F000000161600FDA5D8127912 - MerryIoT
uint64_t JoinEUI = (uint64_t)data[ 1] | ((uint64_t)data[ 2] << 8) |
((uint64_t)data[ 3] << 16) | ((uint64_t)data[ 4] << 24) |
((uint64_t)data[ 5] << 32) | ((uint64_t)data[ 6] << 40) |
((uint64_t)data[ 7] << 48) | ((uint64_t)data[ 8] << 56);
uint32_t DevEUIl = (uint32_t)data[ 9] | ((uint32_t)data[10] << 8) | // Use uint32_t to fix easy JSON migration
((uint32_t)data[11] << 16) | ((uint32_t)data[12] << 24);
uint32_t DevEUIh = (uint32_t)data[13] | ((uint32_t)data[14] << 8) |
((uint32_t)data[15] << 16) | ((uint32_t)data[16] << 24);
uint16_t DevNonce = (uint16_t)data[17] | ((uint16_t)data[18] << 8);
uint32_t MIC = (uint32_t)data[19] | ((uint32_t)data[20] << 8) |
((uint32_t)data[21] << 16) | ((uint32_t)data[22] << 24);
for (uint32_t node = 0; node < TAS_LORAWAN_ENDNODES; node++) {
uint32_t CalcMIC = LoraWanGenerateMIC(data, 19, Lora->settings.end_node[node].AppKey);
if (MIC == CalcMIC) { // Valid MIC based on LoraWanAppKey
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: JoinEUI %8_H, DevEUIh %08X, DevEUIl %08X, DevNonce %04X, MIC %08X"),
(uint8_t*)&JoinEUI, DevEUIh, DevEUIl, DevNonce, MIC);
Lora->settings.end_node[node].DevEUIl = DevEUIl;
Lora->settings.end_node[node].DevEUIh = DevEUIh;
Lora->settings.end_node[node].DevNonce = DevNonce;
Lora->settings.end_node[node].FCntUp = 0;
Lora->settings.end_node[node].FCntDown = 0;
bitClear(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
if (Lora->settings.end_node[node].name.equals(F("0x0000"))) {
char name[10];
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node].DevEUIl & 0x0000FFFF);
Lora->settings.end_node[node].name = name;
}
uint32_t JoinNonce = TAS_LORAWAN_JOINNONCE +node;
uint32_t DevAddr = Lora->device_address +node;
uint32_t NetID = TAS_LORAWAN_NETID;
uint8_t join_data[33] = { 0 };
join_data[0] = TAS_LORAWAN_MTYPE_JOIN_ACCEPT << 5;
join_data[1] = JoinNonce;
join_data[2] = JoinNonce >> 8;
join_data[3] = JoinNonce >> 16;
join_data[4] = NetID;
join_data[5] = NetID >> 8;
join_data[6] = NetID >> 16;
join_data[7] = DevAddr;
join_data[8] = DevAddr >> 8;
join_data[9] = DevAddr >> 16;
join_data[10] = DevAddr >> 24;
join_data[11] = LoraWanSpreadingFactorToDataRate(); // DLSettings
join_data[12] = 1; // RXDelay;
uint32_t NewMIC = LoraWanGenerateMIC(join_data, 13, Lora->settings.end_node[node].AppKey);
join_data[13] = NewMIC;
join_data[14] = NewMIC >> 8;
join_data[15] = NewMIC >> 16;
join_data[16] = NewMIC >> 24;
uint8_t EncData[33];
EncData[0] = join_data[0];
LoraWanEncryptJoinAccept(Lora->settings.end_node[node].AppKey, &join_data[1], 16, &EncData[1]);
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Join %17_H"), join_data);
// 203106E5000000412E010003017CB31DD4 - Dragino
// 203206E5000000422E010003016A210EEA - MerryIoT
LoraWanSendResponse(EncData, 17, TAS_LORAWAN_JOIN_ACCEPT_DELAY1);
result = true;
break;
}
}
}
else if ((TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_UPLINK == MType) ||
(TAS_LORAWAN_MTYPE_CONFIRMED_DATA_UPLINK == MType)) {
// 0 1 2 3 4 5 6 7 8 9 8 9101112131415... packet_size -4
// PHYPayload --------------------------------------------------------------
// MHDR MACPayload ---------------------------------------------- MIC ----
// MHDR FHDR ----------------------- FPort FRMPayload --------- MIC ----
// MHDR DevAddr FCtrl FCnt FOpts FPort FRMPayload --------- MIC ----
// 1 4 1 2 0..15 0..1 0..N 4 - Number of octets
// Not encrypted --------------------------- Encrypted ---------- Not encr
// - Dragino
// 40 412E0100 80 2500 0A 6A6FEFD6A16B0C7AC37B 5F95FABC - decrypt using AppSKey
// 80 412E0100 80 2A00 0A A58EF5E0D1DDE03424F0 6F2D56FA - decrypt using AppSKey
// 80 412E0100 80 2B00 0A 8F2F0D33E5C5027D57A6 F67C9DFE - decrypt using AppSKey
// 80 909AE100 00 0800 0A EEC4A52568A346A8684E F2D4BF05
// 40 412E0100 A0 1800 00 0395 2C94B1D8 - FCtrl ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey
// 40 412E0100 A0 7800 00 78C9 A60D8977 - FCtrl ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey
// 40 F3F51700 20 0100 00 2A7C 407036A2 - FCtrl No ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey, response after LinkADRReq
// - MerryIoT
// 40 422E0100 80 0400 78 B9C75DF9E8934C6651 A57DA6B1 - decrypt using AppSKey
// 40 422E0100 80 0100 CC 7C462537AC00C07F99 5500BF2B - decrypt using AppSKey
// 40 422E0100 A2 1800 0307 78 29FBF8FD9227729984 8C71E95B - FCtrl ADR support, Ack, FOptsLen = 2 -> FOpts = MAC, response after LinkADRReq
// 40 F4F51700 A2 0200 0307 CC 6517D4AB06D32C9A9F 14CBA305 - FCtrl ADR support, Ack, FOptsLen = 2 -> FOpts = MAC, response after LinkADRReq
uint32_t DevAddr = (uint32_t)data[1] | ((uint32_t)data[2] << 8) | ((uint32_t)data[3] << 16) | ((uint32_t)data[4] << 24);
for (uint32_t node = 0; node < TAS_LORAWAN_ENDNODES; node++) {
if (0 == Lora->settings.end_node[node].DevEUIh) { continue; } // No DevEUI so never joined
if ((Lora->device_address +node) != DevAddr) { continue; } // Not my device
uint32_t FCtrl = data[5];
uint32_t FOptsLen = FCtrl & 0x0F;
uint32_t FCnt = (Lora->settings.end_node[node].FCntUp & 0xFFFF0000) | data[6] | (data[7] << 8);
uint8_t* FOpts = &data[8];
uint32_t FPort = data[8 +FOptsLen];
uint8_t* FRMPayload = &data[9 +FOptsLen];
uint32_t payload_len = packet_size -9 -FOptsLen -4;
uint32_t MIC = (uint32_t)data[packet_size -4] | ((uint32_t)data[packet_size -3] << 8) | ((uint32_t)data[packet_size -2] << 16) | ((uint32_t)data[packet_size -1] << 24);
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
uint32_t CalcMIC = LoraWanComputeLegacyUplinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4);
if (MIC != CalcMIC) { continue; } // Same device address but never joined
bool FCtrl_ADR = bitRead(FCtrl, 7);
bool FCtrl_ACK = bitRead(FCtrl, 5);
/*
if ((0 == FOptsLen) && (0 == FOpts[0])) { // MAC response
FOptsLen = payload_len;
FOpts = &data[9];
payload_len = 0;
}
*/
uint8_t payload_decrypted[TAS_LORAWAN_AES128_KEY_SIZE +9 +payload_len];
if (payload_len) {
uint8_t Key[TAS_LORAWAN_AES128_KEY_SIZE];
if (0 == FPort) {
LoraWanDeriveLegacyNwkSKey(node, Key);
} else {
LoraWanDeriveLegacyAppSKey(node, Key);
}
LoraWanDecryptUplink(Key, DevAddr, FCnt, FRMPayload, payload_len, 0, payload_decrypted);
}
uint32_t org_payload_len = payload_len; // Save for logging
if ((0 == FOptsLen) && (0 == FOpts[0])) { // MAC response in payload only
FOptsLen = payload_len; // Payload length is MAC length
FOpts = payload_decrypted; // Payload is encrypted MAC
payload_len = 0; // Payload is MAC only
}
#ifdef USE_LORA_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: DevAddr %08X, FCtrl %0X, FOptsLen %d, FCnt %d, FOpts %*_H, FPort %d, Payload %*_H, Decrypted %*_H, MIC %08X"),
DevAddr, FCtrl, FOptsLen, FCnt, FOptsLen, FOpts, FPort, org_payload_len, FRMPayload, org_payload_len, payload_decrypted, MIC);
#endif // USE_LORA_DEBUG
if (Lora->settings.end_node[node].FCntUp <= FCnt) { // Skip re-transmissions
Lora->rx = false; // Skip RX2 as this is a response from RX1
Lora->settings.end_node[node].FCntUp++;
if (Lora->settings.end_node[node].FCntUp < FCnt) { // Report missed frames
uint32_t FCnt_missed = FCnt - Lora->settings.end_node[node].FCntUp;
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Missed frames %d"), FCnt_missed);
if (FCnt_missed > 1) { // Missed two or more frames
bitClear(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); // Resend LinkADRReq
}
}
Lora->settings.end_node[node].FCntUp = FCnt;
if (FOptsLen) {
uint8_t mac_data[16];
uint32_t mac_data_idx = 0;
uint32_t i = 0;
while (i < FOptsLen) {
if (TAS_LORAWAN_CID_LINK_CHECK_REQ == FOpts[i]) {
// Used by end-device to validate it's connectivity to a network
// Need to send Margin/GWCnt
if (mac_data_idx < 13) {
mac_data[mac_data_idx++] = TAS_LORAWAN_CID_LINK_CHECK_ANS;
int snr = Lora->snr;
if (snr < 0) { snr = 0; }
mac_data[mac_data_idx++] = snr; // Margin - Demodulation margin
mac_data[mac_data_idx++] = 1; // GwCnt - Gateway count
}
}
else if (TAS_LORAWAN_CID_LINK_ADR_ANS == FOpts[i]) {
i++;
uint8_t status = FOpts[i];
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: MAC LinkADRAns PowerACK %d, DataRateACK %d, ChannelMaskACK %d"),
bitRead(status, 2), bitRead(status, 1), bitRead(status, 0));
bitSet(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
}
else if (TAS_LORAWAN_CID_DUTY_CYCLE_ANS == FOpts[i]) {
i++;
}
else if (TAS_LORAWAN_CID_RX_PARAM_SETUP_ANS == FOpts[i]) {
i++;
}
else if (TAS_LORAWAN_CID_DEV_STATUS_ANS == FOpts[i]) {
i++;
i++;
}
else if (TAS_LORAWAN_CID_NEW_CHANNEL_ANS == FOpts[i]) {
i++;
}
else if (TAS_LORAWAN_CID_RX_TIMING_SETUP_ANS == FOpts[i]) {
}
else if (TAS_LORAWAN_CID_TX_PARAM_SETUP_ANS == FOpts[i]) {
}
else if (TAS_LORAWAN_CID_DL_CHANNEL_ANS == FOpts[i]) {
}
else if (TAS_LORAWAN_CID_DEVICE_TIME_REQ == FOpts[i]) {
// Used by the end-device to request the current GPS time
// Need to send epoch/fractional second
if (mac_data_idx < 10) {
mac_data[mac_data_idx++] = TAS_LORAWAN_CID_DEVICE_TIME_ANS;
// ToDo - need to find a way how to calculate the leap seconds
uint32_t gps_time = UtcTime() -315964800 -18 +1; // Time at RX1
mac_data[mac_data_idx++] = gps_time;
mac_data[mac_data_idx++] = gps_time >> 8;
mac_data[mac_data_idx++] = gps_time >> 16;
mac_data[mac_data_idx++] = gps_time >> 24;
// uint32_t frac_time = RtcMillis();
mac_data[mac_data_idx++] = 0x00; // Fractional-second 1/256 sec
}
}
else {
// RFU
}
i++;
}
if (mac_data_idx > 0) {
LoraWanSendMacResponse(node, mac_data, mac_data_idx);
}
}
if (payload_len) {
LoraNodeData_t node_data;
node_data.rssi = Lora->rssi;
node_data.snr = Lora->snr;
node_data.payload = payload_decrypted;
node_data.payload_len = payload_len;
node_data.node = node;
node_data.FPort = FPort;
LoraWanDecode(&node_data);
if (0xA840410E == Lora->settings.end_node[node].DevEUIh) { // Dragino
// Dragino v1.7 fails to set DR with ADR so set it using serial interface:
// Password 123456
// AT+CHS=868100000
// Start join using reset button
// AT+CADR=0
// AT+CDATARATE=3
bitSet(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
}
if (TAS_LORAWAN_MTYPE_CONFIRMED_DATA_UPLINK == MType) {
data[0] = TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_DOWNLINK << 5;
data[5] |= 0x20; // FCtrl Set ACK bit
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++;
data[6] = FCnt;
data[7] = FCnt >> 8;
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4);
data[packet_size -4] = MIC;
data[packet_size -3] = MIC >> 8;
data[packet_size -2] = MIC >> 16;
data[packet_size -1] = MIC >> 24;
LoraWanSendResponse(data, packet_size, TAS_LORAWAN_RECEIVE_DELAY1);
}
}
if (TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_UPLINK == MType) {
if (!bitRead(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ) &&
FCtrl_ADR && !FCtrl_ACK) {
// Try to fix single channel and datarate
LoraWanSendLinkADRReq(node); // Resend LinkADRReq
}
}
}
result = true;
break;
}
}
Lora->receive_time = 0;
return result;
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
#define D_CMND_LORAWANBRIDGE "Bridge"
#define D_CMND_LORAWANAPPKEY "AppKey"
#define D_CMND_LORAWANNAME "Name"
const char kLoraWanCommands[] PROGMEM = "LoRaWan|" // Prefix
D_CMND_LORAWANBRIDGE "|" D_CMND_LORAWANAPPKEY "|" D_CMND_LORAWANNAME;
void (* const LoraWanCommand[])(void) PROGMEM = {
&CmndLoraWanBridge, &CmndLoraWanAppKey, &CmndLoraWanName };
void CmndLoraWanBridge(void) {
// LoraWanBridge - Show LoraOption1
// LoraWanBridge 1 - Set LoraOption1 1 = Enable LoraWanBridge
uint32_t pindex = 0;
if (XdrvMailbox.payload >= 0) {
bitWrite(Lora->settings.flags, pindex, XdrvMailbox.payload);
}
#ifdef USE_LORAWAN_TEST
LoraWanTestKeyDerivation();
LorWanTestUplinkDownlinkEncryption();
LorWanTestUplinkDownlinkMIC();
#endif // USE_LORAWAN_TEST
ResponseCmndChar(GetStateText(bitRead(Lora->settings.flags, pindex)));
}
void CmndLoraWanAppKey(void) {
// LoraWanAppKey
// LoraWanAppKey2 0123456789abcdef0123456789abcdef
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) {
uint32_t node = XdrvMailbox.index -1;
if (32 == XdrvMailbox.data_len) {
HexToBytes(XdrvMailbox.data, Lora->settings.end_node[node].AppKey, TAS_LORAWAN_AES128_KEY_SIZE);
if (0 == Lora->settings.end_node[node].name.length()) {
Lora->settings.end_node[node].name = F("0x0000");
}
}
else if (0 == XdrvMailbox.payload) {
memset(&Lora->settings.end_node[node], 0, sizeof(LoraEndNode_t));
}
char appkey[33];
ext_snprintf_P(appkey, sizeof(appkey), PSTR("%16_H"), Lora->settings.end_node[node].AppKey);
ResponseCmndIdxChar(appkey);
}
}
void CmndLoraWanName(void) {
// LoraWanName
// LoraWanName 1 - Set to short DevEUI (or 0x0000 if not yet joined)
// LoraWanName2 LDS02a
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) {
uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) {
if (1 == XdrvMailbox.payload) {
char name[10];
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node].DevEUIl & 0x0000FFFF);
Lora->settings.end_node[node].name = name;
} else {
Lora->settings.end_node[node].name = XdrvMailbox.data;
}
}
ResponseCmndIdxChar(Lora->settings.end_node[node].name.c_str());
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
void LoraWanInit(void) {
// The Things Network has been assigned a 7-bits "device address prefix" a.k.a. NwkID
// %0010011. Using that, TTN currently sends NetID 0x000013, and a TTN DevAddr always
// starts with 0x26 or 0x27
// Private networks are supposed to used NetID 0x000000.
Lora->device_address = (TAS_LORAWAN_NETID << 25) | (ESP_getChipId() & 0x01FFFFFF);
}
#endif // USE_LORAWAN_BRIDGE
#endif // USE_SPI_LORA