Increase number of supported LoRaWan nodes from 4 to 16

This commit is contained in:
Theo Arends 2025-05-23 13:40:30 +02:00
parent b684486570
commit f6bf4351f5
6 changed files with 120 additions and 90 deletions

View File

@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file.
- ESP32 Platform from 2025.04.30 to 2025.05.40, Framework (Arduino Core) from v3.1.3.250411 to v3.2.0.250504 and IDF from v5.3.2.250403 to v5.4.1.250501 (#23397) - ESP32 Platform from 2025.04.30 to 2025.05.40, Framework (Arduino Core) from v3.1.3.250411 to v3.2.0.250504 and IDF from v5.3.2.250403 to v5.4.1.250501 (#23397)
- ESP32 Platform from 2025.05.40 to 2025.05.30, Framework (Arduino Core) from v3.2.0.250504 to v3.1.3.250504 and IDF from v5.4.1.250501 to v5.3.3.250501 (#23404) - ESP32 Platform from 2025.05.40 to 2025.05.30, Framework (Arduino Core) from v3.2.0.250504 to v3.1.3.250504 and IDF from v5.4.1.250501 to v5.3.3.250501 (#23404)
- ESP8266 platform update from 2024.09.00 to 2025.05.00 (#23448) - ESP8266 platform update from 2024.09.00 to 2025.05.00 (#23448)
- Increase number of supported LoRaWan nodes from 4 to 16
### Fixed ### Fixed
- Haspmota `haspmota.parse()` page parsing (#23403) - Haspmota `haspmota.parse()` page parsing (#23403)

View File

@ -140,6 +140,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- ESP32 Platform from 2025.04.30 to 2025.05.30, Framework (Arduino Core) from v3.1.3.250411 to v3.1.3.250504 and IDF from v5.3.2.250403 to v5.3.3.250501 [#23404](https://github.com/arendst/Tasmota/issues/23404) - ESP32 Platform from 2025.04.30 to 2025.05.30, Framework (Arduino Core) from v3.1.3.250411 to v3.1.3.250504 and IDF from v5.3.2.250403 to v5.3.3.250501 [#23404](https://github.com/arendst/Tasmota/issues/23404)
- GPIOViewer from v1.6.2 to v1.6.3 (No functional change) - GPIOViewer from v1.6.2 to v1.6.3 (No functional change)
- Allow command `WebRefresh` minimum from 1000 to 400 mSec - Allow command `WebRefresh` minimum from 1000 to 400 mSec
- Increase number of supported LoRaWan nodes from 4 to 16
### Fixed ### Fixed
- DNS setting with `IPAddress4/5` not persisted [#23426](https://github.com/arendst/Tasmota/issues/23426) - DNS setting with `IPAddress4/5` not persisted [#23426](https://github.com/arendst/Tasmota/issues/23426)

View File

@ -166,7 +166,7 @@
#define TAS_LORAWAN_RECEIVE_DELAY2 1000 // LoRaWan Receive delay 2 #define TAS_LORAWAN_RECEIVE_DELAY2 1000 // LoRaWan Receive delay 2
#define TAS_LORAWAN_JOIN_ACCEPT_DELAY1 5000 // LoRaWan Join accept delay 1 #define TAS_LORAWAN_JOIN_ACCEPT_DELAY1 5000 // LoRaWan Join accept delay 1
#define TAS_LORAWAN_JOIN_ACCEPT_DELAY2 1000 // LoRaWan Join accept delay 2 #define TAS_LORAWAN_JOIN_ACCEPT_DELAY2 1000 // LoRaWan Join accept delay 2
#define TAS_LORAWAN_ENDNODES 4 // Max number of supported endnodes #define TAS_LORAWAN_ENDNODES 16 // Max number of supported endnodes (every active node uses 68+ bytes)
#define TAS_LORAWAN_AES128_KEY_SIZE 16 // Size in bytes #define TAS_LORAWAN_AES128_KEY_SIZE 16 // Size in bytes
/*********************************************************************************************/ /*********************************************************************************************/
@ -290,7 +290,7 @@ typedef struct LoraSettings_t {
uint8_t flags; uint8_t flags;
uint8_t region; // 0 = Default, 1 = AU915, ... uint8_t region; // 0 = Default, 1 = AU915, ...
#ifdef USE_LORAWAN_BRIDGE #ifdef USE_LORAWAN_BRIDGE
LoraEndNode_t end_node[TAS_LORAWAN_ENDNODES]; // End node parameters LoraEndNode_t *end_node[TAS_LORAWAN_ENDNODES]; // End node parameters
#endif // USE_LORAWAN_BRIDGE #endif // USE_LORAWAN_BRIDGE
} LoraSettings_t; } LoraSettings_t;
@ -314,6 +314,7 @@ typedef struct Lora_t {
uint8_t* send_buffer; uint8_t* send_buffer;
uint8_t send_buffer_step; uint8_t send_buffer_step;
uint8_t send_buffer_len; uint8_t send_buffer_len;
uint8_t nodes;
bool rx; bool rx;
bool send_request; bool send_request;
bool profile_changed; bool profile_changed;

View File

@ -69,7 +69,7 @@ void _LoraWanDeriveLegacyAppSKey(uint8_t* key, uint32_t jn, uint32_t nid, uint16
} }
void LoraWanDeriveLegacyAppSKey(uint32_t node, uint8_t* AppSKey) { void LoraWanDeriveLegacyAppSKey(uint32_t node, uint8_t* AppSKey) {
_LoraWanDeriveLegacyAppSKey(Lora->settings.end_node[node].AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, Lora->settings.end_node[node].DevNonce, AppSKey); _LoraWanDeriveLegacyAppSKey(Lora->settings.end_node[node]->AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, Lora->settings.end_node[node]->DevNonce, AppSKey);
} }
// DeriveLegacyNwkSKey derives the LoRaWAN 1.0 Network Session Key. AppNonce is entered as JoinNonce. // DeriveLegacyNwkSKey derives the LoRaWAN 1.0 Network Session Key. AppNonce is entered as JoinNonce.
@ -82,7 +82,7 @@ void _LoraWanDeriveLegacyNwkSKey(uint8_t* appKey, uint32_t jn, uint32_t nid, uin
} }
void LoraWanDeriveLegacyNwkSKey(uint32_t node, uint8_t* NwkSKey) { void LoraWanDeriveLegacyNwkSKey(uint32_t node, uint8_t* NwkSKey) {
_LoraWanDeriveLegacyNwkSKey(Lora->settings.end_node[node].AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, Lora->settings.end_node[node].DevNonce, NwkSKey); _LoraWanDeriveLegacyNwkSKey(Lora->settings.end_node[node]->AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, Lora->settings.end_node[node]->DevNonce, NwkSKey);
} }
#ifdef USE_LORAWAN_TEST #ifdef USE_LORAWAN_TEST

View File

@ -29,11 +29,11 @@ void LoraWanPublishHeader(uint32_t node) {
} }
if (!Settings->flag5.zb_omit_json_addr) { // SetOption119 - (Zigbee) Remove the device addr from json payload, can be used with zb_topic_fname where the addr is already known from the topic if (!Settings->flag5.zb_omit_json_addr) { // SetOption119 - (Zigbee) Remove the device addr from json payload, can be used with zb_topic_fname where the addr is already known from the topic
ResponseAppend_P(PSTR("{\"%s\":"), EscapeJSONString(Lora->settings.end_node[node].name.c_str()).c_str()); ResponseAppend_P(PSTR("{\"%s\":"), EscapeJSONString(Lora->settings.end_node[node]->name.c_str()).c_str());
} }
ResponseAppend_P(PSTR("{\"Node\":%d,\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), node +1, Lora->settings.end_node[node].DevEUIl & 0x0000FFFF); ResponseAppend_P(PSTR("{\"Node\":%d,\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), node +1, Lora->settings.end_node[node]->DevEUIl & 0x0000FFFF);
if (!Lora->settings.end_node[node].name.startsWith(F("0x"))) { if (!Lora->settings.end_node[node]->name.startsWith(F("0x"))) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), EscapeJSONString(Lora->settings.end_node[node].name.c_str()).c_str()); ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), EscapeJSONString(Lora->settings.end_node[node]->name.c_str()).c_str());
} }
ResponseAppend_P(PSTR(",\"RSSI\":%1_f,\"SNR\":%1_f"), &Lora->rssi, &Lora->snr); ResponseAppend_P(PSTR(",\"RSSI\":%1_f,\"SNR\":%1_f"), &Lora->rssi, &Lora->snr);
} }
@ -58,7 +58,7 @@ void LoraWanPublishFooter(uint32_t node) {
char subtopic[TOPSZ]; char subtopic[TOPSZ];
// Clean special characters // Clean special characters
char stemp[TOPSZ]; char stemp[TOPSZ];
strlcpy(stemp, Lora->settings.end_node[node].name.c_str(), sizeof(stemp)); strlcpy(stemp, Lora->settings.end_node[node]->name.c_str(), sizeof(stemp));
MakeValidMqtt(0, stemp); MakeValidMqtt(0, stemp);
if (Settings->flag5.zigbee_hide_bridge_topic) { // SetOption125 - (Zigbee) Hide bridge topic from zigbee topic (use with SetOption89) (1) if (Settings->flag5.zigbee_hide_bridge_topic) { // SetOption125 - (Zigbee) Hide bridge topic from zigbee topic (use with SetOption89) (1)
snprintf_P(subtopic, sizeof(subtopic), PSTR("%s"), stemp); snprintf_P(subtopic, sizeof(subtopic), PSTR("%s"), stemp);
@ -90,7 +90,7 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) {
uint8_t FPort; uint8_t FPort;
*/ */
if (bitRead(Lora->settings.flags, TAS_LORA_FLAG_DECODE_ENABLED)) { // LoraOption3 1 if (bitRead(Lora->settings.flags, TAS_LORA_FLAG_DECODE_ENABLED)) { // LoraOption3 1
uint32_t manufacturer = Lora->settings.end_node[node_data->node].DevEUIh >> 8; uint32_t manufacturer = Lora->settings.end_node[node_data->node]->DevEUIh >> 8;
if (0x001616 == manufacturer) { // MerryIoT if (0x001616 == manufacturer) { // MerryIoT
if (120 == node_data->FPort) { // MerryIoT door/window Sensor (DW10) if (120 == node_data->FPort) { // MerryIoT door/window Sensor (DW10)
if (9 == node_data->payload_len) { // MerryIoT Sensor state if (9 == node_data->payload_len) { // MerryIoT Sensor state
@ -105,7 +105,7 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) {
uint32_t events = node_data->payload[6] | (node_data->payload[7] << 8) | (node_data->payload[8] << 16); uint32_t events = node_data->payload[6] | (node_data->payload[7] << 8) | (node_data->payload[8] << 16);
#ifdef USE_LORA_DEBUG #ifdef USE_LORA_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Button %d, Tamper %d, Tilt %d, Battery %1_fV, Temp %d, Hum %d"), AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Button %d, Tamper %d, Tilt %d, Battery %1_fV, Temp %d, Hum %d"),
node_data->node +1, Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->node].DevEUIl, node_data->node +1, Lora->settings.end_node[node_data->node]->DevEUIh, Lora->settings.end_node[node_data->node]->DevEUIl,
events, elapsed_time, events, elapsed_time,
bitRead(status, 0), bitRead(status, 1), bitRead(status, 2), bitRead(status, 3), bitRead(status, 0), bitRead(status, 1), bitRead(status, 2), bitRead(status, 3),
&battery_volt, &battery_volt,
@ -137,7 +137,7 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) {
uint8_t alarm = node_data->payload[9]; uint8_t alarm = node_data->payload[9];
#ifdef USE_LORA_DEBUG #ifdef USE_LORA_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Battery %3_fV, Alarm %d"), AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Battery %3_fV, Alarm %d"),
node_data->node +1, Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->node].DevEUIl, node_data->node +1, Lora->settings.end_node[node_data->node]->DevEUIh, Lora->settings.end_node[node_data->node]->DevEUIl,
events, open_duration, events, open_duration,
bitRead(status, 7), bitRead(status, 7),
&battery_volt, &battery_volt,
@ -155,9 +155,9 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) {
// Joined device without decoding // Joined device without decoding
LoraWanPublishHeader(node_data->node); LoraWanPublishHeader(node_data->node);
ResponseAppend_P(PSTR(",\"Decoder\":\"%s\",\"DevEUIh\":\"%08X\",\"DevEUIl\":\"%08X\",\"FPort\":%d,\"Payload\":["), ResponseAppend_P(PSTR(",\"Decoder\":\"%s\",\"DevEUIh\":\"%08X\",\"DevEUIl\":\"%08X\",\"FPort\":%d,\"Payload\":["),
EscapeJSONString(Lora->settings.end_node[node_data->node].decoder.c_str()).c_str(), EscapeJSONString(Lora->settings.end_node[node_data->node]->decoder.c_str()).c_str(),
Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->node]->DevEUIh,
Lora->settings.end_node[node_data->node].DevEUIl, Lora->settings.end_node[node_data->node]->DevEUIl,
node_data->FPort); node_data->FPort);
for (uint32_t i = 0; i < node_data->payload_len; i++) { for (uint32_t i = 0; i < node_data->payload_len; i++) {
ResponseAppend_P(PSTR("%s%d"), (0==i)?"":",", node_data->payload[i]); ResponseAppend_P(PSTR("%s%d"), (0==i)?"":",", node_data->payload[i]);

View File

@ -66,6 +66,21 @@
* LoRaWanBridge 1 * LoRaWanBridge 1
\*********************************************************************************************/ \*********************************************************************************************/
bool LoraWanAddNode(void) {
if (Lora->nodes < TAS_LORAWAN_ENDNODES) {
Lora->settings.end_node[Lora->nodes] = (LoraEndNode_t*)calloc(sizeof(LoraEndNode_t), 1); // Need calloc to reset registers to 0/false
if (Lora->settings.end_node[Lora->nodes]) {
Lora->nodes++;
#ifdef USE_LORA_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Allocated %d bytes for node %d at %08X"),
sizeof(LoraEndNode_t), Lora->nodes, Lora->settings.end_node[Lora->nodes -1]);
#endif // USE_LORA_DEBUG
return true;
}
}
return false;
}
/*********************************************************************************************\ /*********************************************************************************************\
* Driver Settings load and save * Driver Settings load and save
\*********************************************************************************************/ \*********************************************************************************************/
@ -87,34 +102,37 @@ bool LoraWanLoadData(void) {
String json = UfsJsonSettingsRead(key); String json = UfsJsonSettingsRead(key);
if (json.length() == 0) { continue; } // Only load used slots if (json.length() == 0) { continue; } // Only load used slots
// {"AppKey":"00000000000000000000000000000000","DevEUI","0000000000000000","DevNonce":0,"FCntUp":0,"FCntDown":0,"Flags":0,"NAME":""} // {"AppKey":"00000000000000000000000000000000","DevEUIh":1234567890,"DevEUIl":1234567890,"DevNonce":0,"FCntUp":0,"FCntDown":0,"Flags":0,"NAME":"","DCDR":""}
JsonParser parser((char*)json.c_str()); JsonParser parser((char*)json.c_str());
JsonParserObject root = parser.getRootObject(); JsonParserObject root = parser.getRootObject();
if (!root) { continue; } // Only load used slots if (!root) { continue; } // Only load used slots
if (!LoraWanAddNode()) { break; } // Unable to allocate memory
const char* app_key = nullptr; const char* app_key = nullptr;
app_key = root.getStr(PSTR(D_JSON_APPKEY), nullptr); app_key = root.getStr(PSTR(D_JSON_APPKEY), nullptr);
if (app_key && (strlen(app_key))) { if (app_key && (strlen(app_key))) {
HexToBytes(app_key, Lora->settings.end_node[n].AppKey, TAS_LORAWAN_AES128_KEY_SIZE); 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]->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]->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]->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]->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]->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); Lora->settings.end_node[n]->flags = root.getUInt(PSTR(D_JSON_FLAGS), Lora->settings.end_node[n]->flags);
const char* ctemp = root.getStr(PSTR(D_JSON_NAME), nullptr); const char* ctemp = root.getStr(PSTR(D_JSON_NAME), nullptr);
if (ctemp) { Lora->settings.end_node[n].name = ctemp; } if (ctemp) { Lora->settings.end_node[n]->name = ctemp; }
ctemp = root.getStr(PSTR(D_JSON_DCDR), nullptr); ctemp = root.getStr(PSTR(D_JSON_DCDR), nullptr);
if (ctemp) { Lora->settings.end_node[n].decoder = ctemp; } if (ctemp) { Lora->settings.end_node[n]->decoder = ctemp; }
} }
AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: LoraWan loaded %d node(s)"), Lora->nodes);
return true; return true;
} }
bool LoraWanSaveData(void) { bool LoraWanSaveData(void) {
bool result = true; // Return true if no Endnodes bool result = true; // Return true if no Endnodes
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) { for (uint32_t n = 0; n < Lora->nodes; n++) {
if (Lora->settings.end_node[n].AppKey[0] > 0) { // Only save used slots 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\"" Response_P(PSTR("{\"" XDRV_73_KEY "_%d\":{\"" D_JSON_APPKEY "\":\"%16_H\""
",\"" D_JSON_DEVEUI "h\":%lu,\"" D_JSON_DEVEUI "l\":%lu" ",\"" D_JSON_DEVEUI "h\":%lu,\"" D_JSON_DEVEUI "l\":%lu"
",\"" D_JSON_DEVNONCE "\":%u" ",\"" D_JSON_DEVNONCE "\":%u"
@ -123,13 +141,13 @@ bool LoraWanSaveData(void) {
",\"" D_JSON_NAME "\":\"%s\"" ",\"" D_JSON_NAME "\":\"%s\""
",\"" D_JSON_DCDR "\":\"%s\"}}"), ",\"" D_JSON_DCDR "\":\"%s\"}}"),
n +1, n +1,
Lora->settings.end_node[n].AppKey, Lora->settings.end_node[n]->AppKey,
Lora->settings.end_node[n].DevEUIh, Lora->settings.end_node[n].DevEUIl, Lora->settings.end_node[n]->DevEUIh, Lora->settings.end_node[n]->DevEUIl,
Lora->settings.end_node[n].DevNonce, Lora->settings.end_node[n]->DevNonce,
Lora->settings.end_node[n].FCntUp, Lora->settings.end_node[n].FCntDown, Lora->settings.end_node[n]->FCntUp, Lora->settings.end_node[n]->FCntDown,
Lora->settings.end_node[n].flags, Lora->settings.end_node[n]->flags,
(Lora->settings.end_node[n].name) ? Lora->settings.end_node[n].name.c_str() : "", (Lora->settings.end_node[n]->name) ? Lora->settings.end_node[n]->name.c_str() : "",
(Lora->settings.end_node[n].decoder) ? Lora->settings.end_node[n].decoder.c_str() : ""); (Lora->settings.end_node[n]->decoder) ? Lora->settings.end_node[n]->decoder.c_str() : "");
result &= UfsJsonSettingsWrite(ResponseData()); result &= UfsJsonSettingsWrite(ResponseData());
} }
} }
@ -488,6 +506,9 @@ uint32_t LoraWanSpreadingFactorToDataRate(bool downlink) {
return downlink ? 8 : 2; // AU915 must use DR8 for RX2, and we want to use DR2 for Uplinks return downlink ? 8 : 2; // AU915 must use DR8 for RX2, and we want to use DR2 for Uplinks
} }
default: { // TAS_LORA_REGION_EU868 default: { // TAS_LORA_REGION_EU868
if (downlink) {
return 0; // RX2 = 869.525 MHz, DR0 (SF12, 125kHz)
} else {
// Allow only JoinReq message datarates (125kHz bandwidth) // Allow only JoinReq message datarates (125kHz bandwidth)
if (Lora->settings.spreading_factor > 12) { if (Lora->settings.spreading_factor > 12) {
Lora->settings.spreading_factor = 12; Lora->settings.spreading_factor = 12;
@ -499,13 +520,14 @@ uint32_t LoraWanSpreadingFactorToDataRate(bool downlink) {
return 12 - Lora->settings.spreading_factor; return 12 - Lora->settings.spreading_factor;
} }
} }
}
} }
/*********************************************************************************************/ /*********************************************************************************************/
void LoraWanSendLinkADRReq(uint32_t node) { void LoraWanSendLinkADRReq(uint32_t node) {
uint32_t DevAddr = Lora->device_address +node; uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++; uint16_t FCnt = Lora->settings.end_node[node]->FCntDown++;
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE]; uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
LoraWanDeriveLegacyNwkSKey(node, NwkSKey); LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
@ -565,7 +587,7 @@ void LoraWanSendMacResponse(uint32_t node, uint8_t* FOpts, uint32_t FCtrl) {
if (FCtrl > 15) { return; } // FOpts = 0..15 if (FCtrl > 15) { return; } // FOpts = 0..15
uint32_t DevAddr = Lora->device_address +node; uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++; uint16_t FCnt = Lora->settings.end_node[node]->FCntDown++;
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE]; uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
LoraWanDeriveLegacyNwkSKey(node, NwkSKey); LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
@ -611,23 +633,23 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
uint32_t MIC = (uint32_t)data[19] | ((uint32_t)data[20] << 8) | uint32_t MIC = (uint32_t)data[19] | ((uint32_t)data[20] << 8) |
((uint32_t)data[21] << 16) | ((uint32_t)data[22] << 24); ((uint32_t)data[21] << 16) | ((uint32_t)data[22] << 24);
for (uint32_t node = 0; node < TAS_LORAWAN_ENDNODES; node++) { for (uint32_t node = 0; node < Lora->nodes; node++) {
uint32_t CalcMIC = LoraWanGenerateMIC(data, 19, Lora->settings.end_node[node].AppKey); uint32_t CalcMIC = LoraWanGenerateMIC(data, 19, Lora->settings.end_node[node]->AppKey);
if (MIC == CalcMIC) { // Valid MIC based on LoraWanAppKey if (MIC == CalcMIC) { // Valid MIC based on LoraWanAppKey
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, JoinEUI %8_H, DevEUIh %08X, DevEUIl %08X, DevNonce %04X, MIC %08X"), AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, JoinEUI %8_H, DevEUIh %08X, DevEUIl %08X, DevNonce %04X, MIC %08X"),
node +1, (uint8_t*)&JoinEUI, DevEUIh, DevEUIl, DevNonce, MIC); node +1, (uint8_t*)&JoinEUI, DevEUIh, DevEUIl, DevNonce, MIC);
Lora->settings.end_node[node].DevEUIl = DevEUIl; Lora->settings.end_node[node]->DevEUIl = DevEUIl;
Lora->settings.end_node[node].DevEUIh = DevEUIh; Lora->settings.end_node[node]->DevEUIh = DevEUIh;
Lora->settings.end_node[node].DevNonce = DevNonce; Lora->settings.end_node[node]->DevNonce = DevNonce;
Lora->settings.end_node[node].FCntUp = 0; Lora->settings.end_node[node]->FCntUp = 0;
Lora->settings.end_node[node].FCntDown = 0; Lora->settings.end_node[node]->FCntDown = 0;
bitClear(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); bitClear(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
if (Lora->settings.end_node[node].name.equals(F("0x0000"))) { if (Lora->settings.end_node[node]->name.equals(F("0x0000"))) {
char name[10]; char name[10];
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node].DevEUIl & 0x0000FFFF); ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node]->DevEUIl & 0x0000FFFF);
Lora->settings.end_node[node].name = name; Lora->settings.end_node[node]->name = name;
} }
uint32_t JoinNonce = TAS_LORAWAN_JOINNONCE +node; uint32_t JoinNonce = TAS_LORAWAN_JOINNONCE +node;
@ -659,14 +681,14 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
CFListSize--; CFListSize--;
} }
uint32_t NewMIC = LoraWanGenerateMIC(join_data, join_data_index, Lora->settings.end_node[node].AppKey); uint32_t NewMIC = LoraWanGenerateMIC(join_data, join_data_index, Lora->settings.end_node[node]->AppKey);
join_data[join_data_index++] = NewMIC; join_data[join_data_index++] = NewMIC;
join_data[join_data_index++] = NewMIC >> 8; join_data[join_data_index++] = NewMIC >> 8;
join_data[join_data_index++] = NewMIC >> 16; join_data[join_data_index++] = NewMIC >> 16;
join_data[join_data_index++] = NewMIC >> 24; //[16] or [32] join_data[join_data_index++] = NewMIC >> 24; //[16] or [32]
uint8_t EncData[33]; uint8_t EncData[33];
EncData[0] = join_data[0]; EncData[0] = join_data[0];
LoraWanEncryptJoinAccept(Lora->settings.end_node[node].AppKey, &join_data[1], join_data_index-1, &EncData[1]); LoraWanEncryptJoinAccept(Lora->settings.end_node[node]->AppKey, &join_data[1], join_data_index-1, &EncData[1]);
// 203106E5000000412E010003017CB31DD4 - Dragino LDS02 // 203106E5000000412E010003017CB31DD4 - Dragino LDS02
// 2026B4E06C390AFA1B166D465987F31EC4 - Dragino LHT52 // 2026B4E06C390AFA1B166D465987F31EC4 - Dragino LHT52
@ -704,13 +726,13 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
bool bResponseSent = false; // Make sure do not send multiple responses bool bResponseSent = false; // Make sure do not send multiple responses
uint32_t DevAddr = (uint32_t)data[1] | ((uint32_t)data[2] << 8) | ((uint32_t)data[3] << 16) | ((uint32_t)data[4] << 24); 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++) { for (uint32_t node = 0; node < Lora->nodes; node++) {
if (0 == Lora->settings.end_node[node].DevEUIh) { continue; } // No DevEUI so never joined if (0 == Lora->settings.end_node[node]->DevEUIh) { continue; } // No DevEUI so never joined
if ((Lora->device_address +node) != DevAddr) { continue; } // Not my device if ((Lora->device_address +node) != DevAddr) { continue; } // Not my device
uint32_t FCtrl = data[5]; uint32_t FCtrl = data[5];
uint32_t FOptsLen = FCtrl & 0x0F; uint32_t FOptsLen = FCtrl & 0x0F;
uint32_t FCnt = (Lora->settings.end_node[node].FCntUp & 0xFFFF0000) | data[6] | (data[7] << 8); uint32_t FCnt = (Lora->settings.end_node[node]->FCntUp & 0xFFFF0000) | data[6] | (data[7] << 8);
uint8_t* FOpts = &data[8]; uint8_t* FOpts = &data[8];
uint32_t FPort = data[8 +FOptsLen]; uint32_t FPort = data[8 +FOptsLen];
uint8_t* FRMPayload = &data[9 +FOptsLen]; uint8_t* FRMPayload = &data[9 +FOptsLen];
@ -761,17 +783,17 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
DevAddr, FCtrl, FOptsLen, FCnt, FOptsLen, FOpts, FPort, org_payload_len, FRMPayload, org_payload_len, payload_decrypted, MIC); DevAddr, FCtrl, FOptsLen, FCnt, FOptsLen, FOpts, FPort, org_payload_len, FRMPayload, org_payload_len, payload_decrypted, MIC);
#endif // USE_LORA_DEBUG #endif // USE_LORA_DEBUG
if (Lora->settings.end_node[node].FCntUp <= FCnt) { // Skip re-transmissions if (Lora->settings.end_node[node]->FCntUp <= FCnt) { // Skip re-transmissions
Lora->rx = false; // Skip RX2 as this is a response from RX1 Lora->rx = false; // Skip RX2 as this is a response from RX1
Lora->settings.end_node[node].FCntUp++; Lora->settings.end_node[node]->FCntUp++;
if (Lora->settings.end_node[node].FCntUp < FCnt) { // Report missed frames if (Lora->settings.end_node[node]->FCntUp < FCnt) { // Report missed frames
uint32_t FCnt_missed = FCnt - Lora->settings.end_node[node].FCntUp; uint32_t FCnt_missed = FCnt - Lora->settings.end_node[node]->FCntUp;
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Missed frames %d"), FCnt_missed); AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Missed frames %d"), FCnt_missed);
if (FCnt_missed > 1) { // Missed two or more frames if (FCnt_missed > 1) { // Missed two or more frames
bitClear(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); // Resend LinkADRReq bitClear(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); // Resend LinkADRReq
} }
} }
Lora->settings.end_node[node].FCntUp = FCnt; Lora->settings.end_node[node]->FCntUp = FCnt;
if (FOptsLen) { if (FOptsLen) {
uint8_t mac_data[16]; uint8_t mac_data[16];
@ -794,7 +816,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
uint8_t status = FOpts[i]; uint8_t status = FOpts[i];
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: MAC LinkADRAns PowerACK %d, DataRateACK %d, ChannelMaskACK %d"), AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: MAC LinkADRAns PowerACK %d, DataRateACK %d, ChannelMaskACK %d"),
bitRead(status, 2), bitRead(status, 1), bitRead(status, 0)); bitRead(status, 2), bitRead(status, 1), bitRead(status, 0));
bitSet(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); bitSet(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
} }
else if (TAS_LORAWAN_CID_DUTY_CYCLE_ANS == FOpts[i]) { else if (TAS_LORAWAN_CID_DUTY_CYCLE_ANS == FOpts[i]) {
i++; i++;
@ -851,20 +873,20 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
node_data.FPort = FPort; node_data.FPort = FPort;
LoraWanDecode(&node_data); LoraWanDecode(&node_data);
if (0xA84041 == Lora->settings.end_node[node].DevEUIh >> 8) { // Dragino if (0xA84041 == Lora->settings.end_node[node]->DevEUIh >> 8) { // Dragino
// Dragino v1.7 fails to set DR with ADR so set it using serial interface: // Dragino v1.7 fails to set DR with ADR so set it using serial interface:
// Password 123456 // Password 123456
// AT+CHS=868100000 // AT+CHS=868100000
// Start join using reset button // Start join using reset button
// AT+CADR=0 // AT+CADR=0
// AT+CDATARATE=3 // AT+CDATARATE=3
bitSet(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); bitSet(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ);
} }
if (TAS_LORAWAN_MTYPE_CONFIRMED_DATA_UPLINK == MType) { if (TAS_LORAWAN_MTYPE_CONFIRMED_DATA_UPLINK == MType) {
data[0] = TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_DOWNLINK << 5; data[0] = TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_DOWNLINK << 5;
data[5] |= 0x20; // FCtrl Set ACK bit data[5] |= 0x20; // FCtrl Set ACK bit
uint16_t FCnt = Lora->settings.end_node[node].FCntDown++; uint16_t FCnt = Lora->settings.end_node[node]->FCntDown++;
data[6] = FCnt; data[6] = FCnt;
data[7] = FCnt >> 8; data[7] = FCnt >> 8;
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4); uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4);
@ -877,7 +899,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
} }
} }
if (TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_UPLINK == MType) { if (TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_UPLINK == MType) {
if (!bitRead(Lora->settings.end_node[node].flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ) && if (!bitRead(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ) &&
FCtrl_ADR && !FCtrl_ACK) { FCtrl_ADR && !FCtrl_ACK) {
// Try to fix single channel and datarate // Try to fix single channel and datarate
bResponseSent = true; bResponseSent = true;
@ -932,19 +954,24 @@ void CmndLoraWanBridge(void) {
void CmndLoraWanAppKey(void) { void CmndLoraWanAppKey(void) {
// LoraWanAppKey // LoraWanAppKey
// LoraWanAppKey2 0123456789abcdef0123456789abcdef // LoraWanAppKey2 0123456789abcdef0123456789abcdef
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes +1)) {
if (Lora->nodes < XdrvMailbox.index) {
if (!LoraWanAddNode()) {
return; // Memory allocation failed or TAS_LORAWAN_ENDNODES reached
}
}
uint32_t node = XdrvMailbox.index -1; uint32_t node = XdrvMailbox.index -1;
if (32 == XdrvMailbox.data_len) { if (32 == XdrvMailbox.data_len) {
HexToBytes(XdrvMailbox.data, Lora->settings.end_node[node].AppKey, TAS_LORAWAN_AES128_KEY_SIZE); HexToBytes(XdrvMailbox.data, Lora->settings.end_node[node]->AppKey, TAS_LORAWAN_AES128_KEY_SIZE);
if (0 == Lora->settings.end_node[node].name.length()) { if (0 == Lora->settings.end_node[node]->name.length()) {
Lora->settings.end_node[node].name = F("0x0000"); Lora->settings.end_node[node]->name = F("0x0000");
} }
} }
else if (0 == XdrvMailbox.payload) { else if (0 == XdrvMailbox.payload) {
memset(&Lora->settings.end_node[node], 0, sizeof(LoraEndNode_t)); memset(Lora->settings.end_node[node], 0, sizeof(LoraEndNode_t));
} }
char appkey[33]; char appkey[33];
ext_snprintf_P(appkey, sizeof(appkey), PSTR("%16_H"), Lora->settings.end_node[node].AppKey); ext_snprintf_P(appkey, sizeof(appkey), PSTR("%16_H"), Lora->settings.end_node[node]->AppKey);
ResponseCmndIdxChar(appkey); ResponseCmndIdxChar(appkey);
} }
} }
@ -953,18 +980,18 @@ void CmndLoraWanName(void) {
// LoraWanName // LoraWanName
// LoraWanName 1 - Set to short DevEUI (or 0x0000 if not yet joined) // LoraWanName 1 - Set to short DevEUI (or 0x0000 if not yet joined)
// LoraWanName2 LDS02a // LoraWanName2 LDS02a
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
uint32_t node = XdrvMailbox.index -1; uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) { if (XdrvMailbox.data_len) {
if (1 == XdrvMailbox.payload) { if (1 == XdrvMailbox.payload) {
char name[10]; char name[10];
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node].DevEUIl & 0x0000FFFF); ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), Lora->settings.end_node[node]->DevEUIl & 0x0000FFFF);
Lora->settings.end_node[node].name = name; Lora->settings.end_node[node]->name = name;
} else { } else {
Lora->settings.end_node[node].name = XdrvMailbox.data; Lora->settings.end_node[node]->name = XdrvMailbox.data;
} }
} }
ResponseCmndIdxChar(Lora->settings.end_node[node].name.c_str()); ResponseCmndIdxChar(Lora->settings.end_node[node]->name.c_str());
} }
} }
@ -972,12 +999,12 @@ void CmndLoraWanDecoder(void) {
// LoraWanDecoder // LoraWanDecoder
// LoraWanDecoder DraginoLDS02 - Set Dragino LDS02 message decoder for node 1 // LoraWanDecoder DraginoLDS02 - Set Dragino LDS02 message decoder for node 1
// LoraWanDecoder2 MerryIoTDW10 - Set MerryIoT DW10 message decoder for node 2 // LoraWanDecoder2 MerryIoTDW10 - Set MerryIoT DW10 message decoder for node 2
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
uint32_t node = XdrvMailbox.index -1; uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) { if (XdrvMailbox.data_len) {
Lora->settings.end_node[node].decoder = XdrvMailbox.data; Lora->settings.end_node[node]->decoder = XdrvMailbox.data;
} }
ResponseCmndIdxChar(Lora->settings.end_node[node].decoder.c_str()); ResponseCmndIdxChar(Lora->settings.end_node[node]->decoder.c_str());
} }
} }