diff --git a/CHANGELOG.md b/CHANGELOG.md index ce779ab9b..42a36a627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.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) +- Increase number of supported LoRaWan nodes from 4 to 16 ### Fixed - Haspmota `haspmota.parse()` page parsing (#23403) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0efdea213..d30440302 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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) - GPIOViewer from v1.6.2 to v1.6.3 (No functional change) - Allow command `WebRefresh` minimum from 1000 to 400 mSec +- Increase number of supported LoRaWan nodes from 4 to 16 ### Fixed - DNS setting with `IPAddress4/5` not persisted [#23426](https://github.com/arendst/Tasmota/issues/23426) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino index 439c2728a..b2d6edae4 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino @@ -166,7 +166,7 @@ #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_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 /*********************************************************************************************/ @@ -290,7 +290,7 @@ typedef struct LoraSettings_t { uint8_t flags; uint8_t region; // 0 = Default, 1 = AU915, ... #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 } LoraSettings_t; @@ -314,6 +314,7 @@ typedef struct Lora_t { uint8_t* send_buffer; uint8_t send_buffer_step; uint8_t send_buffer_len; + uint8_t nodes; bool rx; bool send_request; bool profile_changed; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_4_lorawan_cryptography.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_4_lorawan_cryptography.ino index cbfa04b77..3c3fe7daf 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_4_lorawan_cryptography.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_4_lorawan_cryptography.ino @@ -69,7 +69,7 @@ void _LoraWanDeriveLegacyAppSKey(uint8_t* key, uint32_t jn, uint32_t nid, uint16 } 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. @@ -82,7 +82,7 @@ void _LoraWanDeriveLegacyNwkSKey(uint8_t* appKey, uint32_t jn, uint32_t nid, uin } 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 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino index c47f40fc4..321b57138 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino @@ -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 - 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); - 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("{\"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"))) { + 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); } @@ -58,7 +58,7 @@ void LoraWanPublishFooter(uint32_t node) { char subtopic[TOPSZ]; // Clean special characters 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); 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); @@ -90,7 +90,7 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) { uint8_t FPort; */ 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 (120 == node_data->FPort) { // MerryIoT door/window Sensor (DW10) 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); #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"), - 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, bitRead(status, 0), bitRead(status, 1), bitRead(status, 2), bitRead(status, 3), &battery_volt, @@ -137,7 +137,7 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) { uint8_t alarm = node_data->payload[9]; #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"), - 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, bitRead(status, 7), &battery_volt, @@ -155,9 +155,9 @@ void LoraWanDecode(struct LoraNodeData_t* node_data) { // Joined device without decoding LoraWanPublishHeader(node_data->node); 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(), - Lora->settings.end_node[node_data->node].DevEUIh, - Lora->settings.end_node[node_data->node].DevEUIl, + 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]->DevEUIl, node_data->FPort); for (uint32_t i = 0; i < node_data->payload_len; i++) { ResponseAppend_P(PSTR("%s%d"), (0==i)?"":",", node_data->payload[i]); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino index d3f8bbef1..84497f0ed 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino @@ -66,6 +66,21 @@ * 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 \*********************************************************************************************/ @@ -80,41 +95,44 @@ #define D_JSON_FLAGS "Flags" bool LoraWanLoadData(void) { - char key[12]; // Max 99 nodes (drvset73_1 to drvset73_99) + 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 + 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()); 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; app_key = root.getStr(PSTR(D_JSON_APPKEY), nullptr); 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].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); + 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* 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); - 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; } bool LoraWanSaveData(void) { bool result = true; // Return true if no Endnodes - for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) { - if (Lora->settings.end_node[n].AppKey[0] > 0) { // Only save used slots + for (uint32_t n = 0; n < Lora->nodes; 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" @@ -123,13 +141,13 @@ bool LoraWanSaveData(void) { ",\"" D_JSON_NAME "\":\"%s\"" ",\"" D_JSON_DCDR "\":\"%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) ? 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]->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) ? Lora->settings.end_node[n]->name.c_str() : "", + (Lora->settings.end_node[n]->decoder) ? Lora->settings.end_node[n]->decoder.c_str() : ""); result &= UfsJsonSettingsWrite(ResponseData()); } } @@ -373,7 +391,7 @@ void LoRaWanSend(void) { Lora->send_request = false; LoraSend(Lora->send_buffer, Lora->send_buffer_len, true); if (Lora->profile_changed) { - Lora->settings = Lora->backup_settings; // Restore copy for reception + Lora->settings = Lora->backup_settings; // Restore copy for reception if (0 == Lora->send_buffer_step) { Lora->Init(); // Necessary to re-init the SXxxxx chip in cases where TX/RX frequencies differ } else { @@ -416,7 +434,7 @@ void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) { Lora->send_buffer_len = len; Lora->send_request = false; - Lora->backup_settings = Lora->settings; // Make a copy; + Lora->backup_settings = Lora->settings; // Make a copy; Lora->send_buffer_step = 2; // Send at RX1 and RX2 uint32_t delay_rx1 = lorawan_delay - TimePassedSince(Lora->receive_time); @@ -488,15 +506,19 @@ uint32_t LoraWanSpreadingFactorToDataRate(bool downlink) { return downlink ? 8 : 2; // AU915 must use DR8 for RX2, and we want to use DR2 for Uplinks } default: { // TAS_LORA_REGION_EU868 - // Allow only JoinReq message datarates (125kHz bandwidth) - if (Lora->settings.spreading_factor > 12) { - Lora->settings.spreading_factor = 12; + if (downlink) { + return 0; // RX2 = 869.525 MHz, DR0 (SF12, 125kHz) + } else { + // 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; } - if (Lora->settings.spreading_factor < 7) { - Lora->settings.spreading_factor = 7; - } - Lora->settings.bandwidth = 125; - return 12 - Lora->settings.spreading_factor; } } } @@ -505,7 +527,7 @@ uint32_t LoraWanSpreadingFactorToDataRate(bool downlink) { void LoraWanSendLinkADRReq(uint32_t 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]; 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 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]; 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)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); + for (uint32_t node = 0; node < Lora->nodes; 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: Node %d, JoinEUI %8_H, DevEUIh %08X, DevEUIl %08X, DevNonce %04X, MIC %08X"), node +1, (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"))) { + 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; + 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; @@ -659,14 +681,14 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { 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 >> 8; join_data[join_data_index++] = NewMIC >> 16; join_data[join_data_index++] = NewMIC >> 24; //[16] or [32] uint8_t EncData[33]; 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 // 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 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 + for (uint32_t node = 0; node < Lora->nodes; 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); + 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]; @@ -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); #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->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; + 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 + 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) { uint8_t mac_data[16]; @@ -794,7 +816,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { 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); + bitSet(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_LINK_ADR_REQ); } else if (TAS_LORAWAN_CID_DUTY_CYCLE_ANS == FOpts[i]) { i++; @@ -851,20 +873,20 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { node_data.FPort = FPort; 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: // 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); + 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++; + 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); @@ -877,7 +899,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { } } 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) { // Try to fix single channel and datarate bResponseSent = true; @@ -932,19 +954,24 @@ void CmndLoraWanBridge(void) { void CmndLoraWanAppKey(void) { // LoraWanAppKey // 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; 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"); + 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)); + 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); + ext_snprintf_P(appkey, sizeof(appkey), PSTR("%16_H"), Lora->settings.end_node[node]->AppKey); ResponseCmndIdxChar(appkey); } } @@ -953,18 +980,18 @@ 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)) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) { 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; + 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; + 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 DraginoLDS02 - Set Dragino LDS02 message decoder for node 1 // 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; 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()); } }