From 7fb8654c6c866af34ec6ea7265c4b967fed60f82 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 13 May 2025 16:53:02 +0200 Subject: [PATCH] Add Support for LoRaWan Rx1 and Rx2 profiles (#23394) --- CHANGELOG.md | 1 + RELEASENOTES.md | 1 + tasmota/tasmota.ino | 35 ++-- .../xdrv_73_0_lora_struct.ino | 28 ++- .../xdrv_73_3_lora_sx126x.ino | 31 ++-- .../xdrv_73_3_lora_sx127x.ino | 38 ++-- .../xdrv_73_6_lorawan_decode.ino | 36 ++-- .../xdrv_73_8_lorawan_bridge.ino | 164 +++++++++++------- .../tasmota_xdrv_driver/xdrv_73_9_lora.ino | 27 +-- 9 files changed, 222 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2e8ec68..1620beb4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [14.6.0.2] ### Added - Allow temporary change of DisplayDimmer (#23406) +- Support for LoRaWan Rx1 and Rx2 profiles (#23394) ### Breaking Changed diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cd3fcc95d..6b2e01473 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -119,6 +119,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents - Command `JsonPP |backlog ;...` to enable JSON PP only once - Support for multi channel AU915-928 LoRaWanBridge by Rob Clark [#23372](https://github.com/arendst/Tasmota/issues/23372) +- Support for LoRaWan Rx1 and Rx2 profiles [#23394](https://github.com/arendst/Tasmota/issues/23394) - Allow temporary change of DisplayDimmer [#23406](https://github.com/arendst/Tasmota/issues/23406) - WebUI status line for MQTT and TLS, added `FUNC_WEB_STATUS_LEFT` and `FUNC_WEB_STATUS_RIGHT` event [#23354](https://github.com/arendst/Tasmota/issues/23354) - WebUI heap status [#23356](https://github.com/arendst/Tasmota/issues/23356) diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index f91cde73c..586612497 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -266,7 +266,6 @@ struct TasmotaGlobal_t { uint32_t zc_offset; // Zero cross moment offset due to monitoring chip processing (microseconds) uint32_t zc_code_offset; // Zero cross moment offset due to executing power code (microseconds) uint32_t zc_interval; // Zero cross interval around 8333 (60Hz) or 10000 (50Hz) (microseconds) - uint32_t skip_sleep; // Skip sleep GpioOptionABits gpio_optiona; // GPIO Option_A flags void *log_buffer_mutex; // Control access to log buffer @@ -328,6 +327,7 @@ struct TasmotaGlobal_t { uint8_t user_globals[3]; // User set global temp/hum/press uint8_t busy_time; // Time in ms to allow executing of time critical functions + uint8_t skip_sleep; // Skip sleep loops uint8_t init_state; // Tasmota init state uint8_t heartbeat_inverted; // Heartbeat pulse inverted flag uint8_t spi_enabled; // SPI configured (bus1) @@ -747,20 +747,18 @@ void BacklogLoop(void) { } } -bool SleepSkip(uint32_t no_sleep) { - if (0 == no_sleep) { - return !TimeReached(TasmotaGlobal.skip_sleep); - } - SetNextTimeInterval(TasmotaGlobal.skip_sleep, no_sleep); // Skip sleep for some ms - return true; +void SleepSkip(void) { + TasmotaGlobal.skip_sleep = 250; // Skip sleep for 250 loops; } void SleepDelay(uint32_t mseconds) { - if (SleepSkip(0)) { return; } // Temporarily skip sleep to handle imminent interrupts outside interrupt handler if (!TasmotaGlobal.backlog_nodelay && mseconds) { uint32_t wait = millis() + mseconds; - while (!TimeReached(wait) && !Serial.available() && !SleepSkip(0)) { // We need to service serial buffer ASAP as otherwise we get uart buffer overrun + while (!TasmotaGlobal.skip_sleep && // We need to service imminent interrupts ASAP + !TimeReached(wait) && + !Serial.available()) { // We need to service serial buffer ASAP as otherwise we get uart buffer overrun XdrvXsnsCall(FUNC_SLEEP_LOOP); // Main purpose is reacting ASAP on serial data availability or interrupt handling (ADE7880) + if (TasmotaGlobal.skip_sleep) { break; } delay(1); } } else { @@ -846,19 +844,22 @@ void loop(void) { uint32_t my_activity = millis() - my_sleep; - if (Settings->flag3.sleep_normal) { // SetOption60 - Enable normal sleep instead of dynamic sleep - // yield(); // yield == delay(0), delay contains yield, auto yield in loop - SleepDelay(TasmotaGlobal.sleep); // https://github.com/esp8266/Arduino/issues/2021 + if (TasmotaGlobal.skip_sleep) { + TasmotaGlobal.skip_sleep--; // Temporarily skip sleep to handle imminent interrupts outside interrupt handler } else { - if (my_activity < (uint32_t)TasmotaGlobal.sleep) { - SleepDelay((uint32_t)TasmotaGlobal.sleep - my_activity); // Provide time for background tasks like wifi + if (Settings->flag3.sleep_normal) { // SetOption60 - Enable normal sleep instead of dynamic sleep + // yield(); // yield == delay(0), delay contains yield, auto yield in loop + SleepDelay(TasmotaGlobal.sleep); // https://github.com/esp8266/Arduino/issues/2021 } else { - if (TasmotaGlobal.global_state.network_down) { - SleepDelay(my_activity /2); // If wifi down and my_activity > setoption36 then force loop delay to 1/2 of my_activity period + if (my_activity < (uint32_t)TasmotaGlobal.sleep) { + SleepDelay((uint32_t)TasmotaGlobal.sleep - my_activity); // Provide time for background tasks like wifi + } else { + if (TasmotaGlobal.global_state.network_down) { + SleepDelay(my_activity /2); // If wifi down and my_activity > setoption36 then force loop delay to 1/2 of my_activity period + } } } } - if (!my_activity) { my_activity++; } // We cannot divide by 0 uint32_t loop_delay = TasmotaGlobal.sleep; if (!loop_delay) { loop_delay++; } // We cannot divide by 0 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 72dd9999d..439c2728a 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_0_lora_struct.ino @@ -106,13 +106,30 @@ // EU868 values #ifndef TAS_LORAWAN_FREQUENCY -#define TAS_LORAWAN_FREQUENCY 868.1 // Allowed values range from 150.0 to 960.0 MHz +#define TAS_LORAWAN_FREQUENCY 868.1 // Allowed values are 868.1, 868.3 and 868.5 MHz #endif #ifndef TAS_LORAWAN_BANDWIDTH -#define TAS_LORAWAN_BANDWIDTH 125.0 // Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz +#define TAS_LORAWAN_BANDWIDTH 125.0 // Allowed values are 125.0 and 250.0 kHz #endif #ifndef TAS_LORAWAN_SPREADING_FACTOR -#define TAS_LORAWAN_SPREADING_FACTOR 9 // Allowed values range from 5 to 12 +#define TAS_LORAWAN_SPREADING_FACTOR 9 // Allowed values range from 7 to 12 +#endif + +#ifndef TAS_LORAWAN_BANDWIDTH_RX1 +#define TAS_LORAWAN_BANDWIDTH_RX1 125.0 // DR3 +#endif +#ifndef TAS_LORAWAN_SPREADING_FACTOR_RX1 +#define TAS_LORAWAN_SPREADING_FACTOR_RX1 9 // DR3 +#endif + +#ifndef TAS_LORAWAN_FREQUENCY_DN +#define TAS_LORAWAN_FREQUENCY_DN 869.525 // Class B downlink channel +#endif +#ifndef TAS_LORAWAN_BANDWIDTH_RX2 +#define TAS_LORAWAN_BANDWIDTH_RX2 125.0 // DR0 +#endif +#ifndef TAS_LORAWAN_SPREADING_FACTOR_RX2 +#define TAS_LORAWAN_SPREADING_FACTOR_RX2 12 // DR0 #endif // Common LoRaWan values @@ -278,7 +295,7 @@ typedef struct LoraSettings_t { } LoraSettings_t; typedef struct Lora_t { - bool (* Config)(void); + bool (* Config)(bool); bool (* Available)(void); int (* Receive)(char*); bool (* Send)(uint8_t*, uint32_t, bool); @@ -293,10 +310,13 @@ typedef struct Lora_t { bool raw; #ifdef USE_LORAWAN_BRIDGE uint32_t device_address; + LoraSettings_t backup_settings; uint8_t* send_buffer; uint8_t send_buffer_step; uint8_t send_buffer_len; bool rx; + bool send_request; + bool profile_changed; #endif // USE_LORAWAN_BRIDGE } Lora_t; Lora_t* Lora = nullptr; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx126x.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx126x.ino index 5a6c351c3..b5aca7e22 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx126x.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx126x.ino @@ -46,7 +46,8 @@ bool LoraSx126xBusy(void) { void IRAM_ATTR LoraSx126xOnInterrupt(void); void LoraSx126xOnInterrupt(void) { // This is called after EVERY type of enabled interrupt so chk for valid receivedFlag in LoraAvailableSx126x() - if (!Lora->send_flag && !Lora->received_flag && !Lora->receive_time) { +// if (!Lora->send_flag && !Lora->received_flag && !Lora->receive_time) { + if (!Lora->send_flag && !Lora->received_flag) { Lora->receive_time = millis(); } Lora->received_flag = true; // we got a packet, set the flag @@ -119,29 +120,31 @@ bool LoraSx126xSend(uint8_t* data, uint32_t len, bool invert) { return (RADIOLIB_ERR_NONE == state); } -bool LoraSx126xConfig(void) { - LoRaRadio.setCodingRate(Lora->settings.coding_rate); - LoRaRadio.setSyncWord(Lora->settings.sync_word); - LoRaRadio.setPreambleLength(Lora->settings.preamble_length); - LoRaRadio.setCurrentLimit(Lora->settings.current_limit); - LoRaRadio.setCRC(Lora->settings.crc_bytes); +bool LoraSx126xConfig(bool full) { LoRaRadio.setSpreadingFactor(Lora->settings.spreading_factor); LoRaRadio.setBandwidth(Lora->settings.bandwidth); LoRaRadio.setFrequency(Lora->settings.frequency); - LoRaRadio.setOutputPower(Lora->settings.output_power); - if (Lora->settings.implicit_header) { - LoRaRadio.implicitHeader(Lora->settings.implicit_header); - } else { - LoRaRadio.explicitHeader(); + if (full) { + LoRaRadio.setCodingRate(Lora->settings.coding_rate); + LoRaRadio.setSyncWord(Lora->settings.sync_word); + LoRaRadio.setPreambleLength(Lora->settings.preamble_length); + LoRaRadio.setCurrentLimit(Lora->settings.current_limit); + LoRaRadio.setCRC(Lora->settings.crc_bytes); + LoRaRadio.setOutputPower(Lora->settings.output_power); + if (Lora->settings.implicit_header) { + LoRaRadio.implicitHeader(Lora->settings.implicit_header); + } else { + LoRaRadio.explicitHeader(); + } + LoRaRadio.invertIQ(false); } - LoRaRadio.invertIQ(false); return true; } bool LoraSx126xInit(void) { LoRaRadio = new Module(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_DI1), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_BUSY)); if (RADIOLIB_ERR_NONE == LoRaRadio.begin(Lora->settings.frequency)) { - LoraSx126xConfig(); + LoraSx126xConfig(true); LoRaRadio.setDio1Action(LoraSx126xOnInterrupt); if (RADIOLIB_ERR_NONE == LoRaRadio.startReceive()) { return true; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx127x.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx127x.ino index c05c00110..272da43fa 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx127x.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_3_lora_sx127x.ino @@ -72,35 +72,37 @@ bool LoraSx127xSend(uint8_t* data, uint32_t len, bool invert) { return true; } -bool LoraSx127xConfig(void) { +bool LoraSx127xConfig(bool full) { LoRa.setFrequency(Lora->settings.frequency * 1000 * 1000); LoRa.setSignalBandwidth(Lora->settings.bandwidth * 1000); LoRa.setSpreadingFactor(Lora->settings.spreading_factor); - LoRa.setCodingRate4(Lora->settings.coding_rate); - LoRa.setSyncWord(Lora->settings.sync_word); - LoRa.setTxPower(Lora->settings.output_power); - LoRa.setPreambleLength(Lora->settings.preamble_length); - LoRa.setOCP(Lora->settings.current_limit); - if (Lora->settings.crc_bytes) { - LoRa.enableCrc(); - } else { - LoRa.disableCrc(); - } + if (full) { + LoRa.setCodingRate4(Lora->settings.coding_rate); + LoRa.setSyncWord(Lora->settings.sync_word); + LoRa.setTxPower(Lora->settings.output_power); + LoRa.setPreambleLength(Lora->settings.preamble_length); + LoRa.setOCP(Lora->settings.current_limit); + if (Lora->settings.crc_bytes) { + LoRa.enableCrc(); + } else { + LoRa.disableCrc(); + } /* - if (Lora->settings.implicit_header) { - LoRa.implicitHeaderMode(); - } else { - LoRa.explicitHeaderMode(); - } + if (Lora->settings.implicit_header) { + LoRa.implicitHeaderMode(); + } else { + LoRa.explicitHeaderMode(); + } */ - LoRa.disableInvertIQ(); // normal mode + LoRa.disableInvertIQ(); // normal mode + } return true; } bool LoraSx127xInit(void) { LoRa.setPins(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_DI0)); if (LoRa.begin(Lora->settings.frequency * 1000 * 1000)) { - LoraSx127xConfig(); + LoraSx127xConfig(true); LoRa.onReceive(LoraSx127xOnReceive); LoRa.receive(); return true; 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 ece953fc8..c47f40fc4 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino @@ -53,25 +53,27 @@ void LoraWanPublishFooter(uint32_t node) { InfluxDbProcess(1); // Use a copy of ResponseData #endif - if (Settings->flag4.zigbee_distinct_topics) { // SetOption89 - (MQTT, Zigbee) Distinct MQTT topics per device for Zigbee (1) (#7835) - char subtopic[TOPSZ]; - // Clean special characters - char stemp[TOPSZ]; - 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); + if (!Settings->flag6.mqtt_disable_publish) { // SetOption147 - If it is activated, Tasmota will not publish MQTT messages, but it will proccess event trigger rules + if (Settings->flag4.zigbee_distinct_topics) { // SetOption89 - (MQTT, Zigbee) Distinct MQTT topics per device for Zigbee (1) (#7835) + char subtopic[TOPSZ]; + // Clean special characters + char stemp[TOPSZ]; + 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); + } else { + snprintf_P(subtopic, sizeof(subtopic), PSTR("%s/%s"), TasmotaGlobal.mqtt_topic, stemp); + } + char stopic[TOPSZ]; + if (Settings->flag5.zb_received_as_subtopic) // SetOption118 - (Zigbee) Move LwReceived from JSON message and into the subtopic replacing "SENSOR" default + GetTopic_P(stopic, TELE, subtopic, PSTR("LwReceived")); + else + GetTopic_P(stopic, TELE, subtopic, PSTR(D_RSLT_SENSOR)); + MqttPublish(stopic, Settings->flag.mqtt_sensor_retain); } else { - snprintf_P(subtopic, sizeof(subtopic), PSTR("%s/%s"), TasmotaGlobal.mqtt_topic, stemp); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain); } - char stopic[TOPSZ]; - if (Settings->flag5.zb_received_as_subtopic) // SetOption118 - (Zigbee) Move LwReceived from JSON message and into the subtopic replacing "SENSOR" default - GetTopic_P(stopic, TELE, subtopic, PSTR("LwReceived")); - else - GetTopic_P(stopic, TELE, subtopic, PSTR(D_RSLT_SENSOR)); - MqttPublish(stopic, Settings->flag.mqtt_sensor_retain); - } else { - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain); } XdrvRulesProcess(0); // Apply rules } 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 ea6e07ac8..d3f8bbef1 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_8_lorawan_bridge.ino @@ -187,7 +187,7 @@ uint32_t LoraWanFrequencyToChannel(void) { } if (250 == channel) { Lora->settings.frequency = 868.1; - Lora->Config(); + Lora->Config(false); channel = 0; } } @@ -309,33 +309,50 @@ void LoraWanRadioInfo(uint8_t mode, void* pInfo, uint32_t uChannel = 0) { break; } default: { // TAS_LORA_REGION_EU868 - //Default TX/RX1/TX1 same - (*pResult).frequency = TAS_LORAWAN_FREQUENCY; - (*pResult).bandwidth = TAS_LORAWAN_BANDWIDTH; - (*pResult).spreading_factor = TAS_LORAWAN_SPREADING_FACTOR; + switch (mode) { + case TAS_LORAWAN_RADIO_UPLINK: { + (*pResult).frequency = TAS_LORAWAN_FREQUENCY; + (*pResult).bandwidth = TAS_LORAWAN_BANDWIDTH; + (*pResult).spreading_factor = TAS_LORAWAN_SPREADING_FACTOR; + break; + } + case TAS_LORAWAN_RADIO_RX1: { + // RX1 DR depends on the Uplink settings + // https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf + // Page 19 + (*pResult).frequency = TAS_LORAWAN_FREQUENCY; + (*pResult).bandwidth = TAS_LORAWAN_BANDWIDTH_RX1; + (*pResult).spreading_factor = TAS_LORAWAN_SPREADING_FACTOR_RX1; + break; + } + case TAS_LORAWAN_RADIO_RX2: { + (*pResult).frequency = TAS_LORAWAN_FREQUENCY_DN; + (*pResult).bandwidth = TAS_LORAWAN_BANDWIDTH_RX2; + (*pResult).spreading_factor = TAS_LORAWAN_SPREADING_FACTOR_RX2; + break; + } +// default: +// not implemented + } } } } -bool LoraWanDefaults(uint32_t region = TAS_LORA_REGION_EU868, LoRaWanRadioMode_t mode = TAS_LORAWAN_RADIO_UPLINK) { - bool multi_profile = false; +bool LoraWanProfile(uint32_t mode) { + LoRaWanRadioInfo_t RadioInfo; + LoraWanRadioInfo(mode, &RadioInfo, LoraWanFrequencyToChannel()); // Region specific + bool changed = ((Lora->settings.frequency != RadioInfo.frequency) || + (Lora->settings.bandwidth != RadioInfo.bandwidth) || + (Lora->settings.spreading_factor != RadioInfo.spreading_factor)); + Lora->settings.frequency = RadioInfo.frequency; + Lora->settings.bandwidth = RadioInfo.bandwidth; + Lora->settings.spreading_factor = RadioInfo.spreading_factor; + return changed; +} + +void LoraWanDefaults(uint32_t region) { Lora->settings.region = region; - switch (region) { - case TAS_LORA_REGION_AU915: - // TO DO: Need 3 profiles: Uplink, RX1, RX2 - // Works OK for now as RX2 always received by end device. - multi_profile = true; - LoRaWanRadioInfo_t RadioInfo; - LoraWanRadioInfo(mode, &RadioInfo, LoraWanFrequencyToChannel()); // Region specific - Lora->settings.frequency = RadioInfo.frequency; - Lora->settings.bandwidth = RadioInfo.bandwidth; - Lora->settings.spreading_factor = RadioInfo.spreading_factor; - break; - default: // TAS_LORA_REGION_EU868 - Lora->settings.frequency = TAS_LORAWAN_FREQUENCY; - Lora->settings.bandwidth = TAS_LORAWAN_BANDWIDTH; - Lora->settings.spreading_factor = TAS_LORAWAN_SPREADING_FACTOR; - } + LoraWanProfile(TAS_LORAWAN_RADIO_UPLINK); Lora->settings.coding_rate = TAS_LORAWAN_CODING_RATE; Lora->settings.sync_word = TAS_LORAWAN_SYNC_WORD; Lora->settings.output_power = TAS_LORAWAN_OUTPUT_POWER; @@ -343,45 +360,51 @@ bool LoraWanDefaults(uint32_t region = TAS_LORA_REGION_EU868, LoRaWanRadioMode_t Lora->settings.current_limit = TAS_LORAWAN_CURRENT_LIMIT; Lora->settings.implicit_header = TAS_LORAWAN_HEADER; Lora->settings.crc_bytes = TAS_LORAWAN_CRC_BYTES; - return multi_profile; } /*********************************************************************************************/ #include -Ticker LoraWan_Send; +Ticker LoraWan_Send_RX1; +Ticker LoraWan_Send_RX2; -void LoraWanSend(uint8_t* data, uint32_t len, bool uplink_profile) { - LoraSettings_t RXsettings; - if (uplink_profile) { - // Different TX/RX profiles allowed. (e.g. LoRaWAN) - // For CmndLoraSend() ... do not allow changes. - RXsettings = Lora->settings; // Make a copy; - LoraWanDefaults(Lora->settings.region, TAS_LORAWAN_RADIO_RX2); // Set Downlink profile TO DO: Support different RX1 & RX2 profiles - Lora->Config(); - } - LoraSend(data, len, true); - if (uplink_profile) { - Lora->settings = RXsettings; // Restore copy - Lora->Config(); +void LoRaWanSend(void) { + if (!Lora->send_request) { return; } + 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 + if (0 == Lora->send_buffer_step) { + Lora->Init(); // Necessary to re-init the SXxxxx chip in cases where TX/RX frequencies differ + } else { + Lora->Config(false); + } } } void LoraWanTickerSend(void) { + // Need to stay here as short as possible to not initiate watchdog exception + // As SF12 can take over 1 sec do send in loop instead of here 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 } - - bool uplink_profile = (Lora->settings.region == TAS_LORA_REGION_AU915); if (Lora->rx) { // If received in RX1 do not resend in RX2 - LoraWanSend(Lora->send_buffer, Lora->send_buffer_len, uplink_profile); - } - - if (uplink_profile && (0 == Lora->send_buffer_step)) { - Lora->Init(); // Necessary to re-init the SXxxxx chip in cases where TX/RX frequencies differ + if (1 == Lora->send_buffer_step) { + Lora->profile_changed = LoraWanProfile(TAS_LORAWAN_RADIO_RX1); // Set Downlink RX1 profile + } else { + Lora->profile_changed = LoraWanProfile(TAS_LORAWAN_RADIO_RX2); // Set Downlink RX2 profile + } + if (Lora->profile_changed) { + Lora->Config(false); + } + Lora->send_request = true; // Send in loop fixing watchdogs +#ifdef ESP8266 + SleepSkip(); // Skip sleep +#else // ESP32 + LoRaWanSend(); +#endif // ESP8266/ESP32 } } @@ -391,8 +414,19 @@ void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) { if (nullptr == Lora->send_buffer) { return; } memcpy(Lora->send_buffer, buffer, len); Lora->send_buffer_len = len; + + Lora->send_request = false; + Lora->backup_settings = Lora->settings; // Make a copy; Lora->send_buffer_step = 2; // Send at RX1 and RX2 - LoraWan_Send.once_ms(lorawan_delay - TimePassedSince(Lora->receive_time), LoraWanTickerSend); + + uint32_t delay_rx1 = lorawan_delay - TimePassedSince(Lora->receive_time); + LoraWan_Send_RX1.once_ms(delay_rx1, LoraWanTickerSend); + uint32_t delay_rx2 = delay_rx1 + TAS_LORAWAN_RECEIVE_DELAY2; + LoraWan_Send_RX2.once_ms(delay_rx2, LoraWanTickerSend); // Retry after 1000 ms +#ifdef USE_LORA_DEBUG + AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: About to send '%*_H' in %d and (optional) %d ms"), + Lora->send_buffer_len, Lora->send_buffer, delay_rx1, delay_rx2); +#endif // USE_LORA_DEBUG } /*-------------------------------------------------------------------------------------------*/ @@ -400,12 +434,12 @@ void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) { size_t LoraWanCFList(uint8_t * CFList, size_t uLen) { // Populates CFList for use in JOIN-ACCEPT message to lock device to specific frequencies // Returns: Number of bytes added + if (uLen < 16) return 0; + uint8_t idx = 0; switch (Lora->settings.region) { case TAS_LORA_REGION_AU915: { - if (uLen < 16) return 0; - uint8_t uChannel = LoraWanFrequencyToChannel(); // 0..71 uint8_t uMaskByte = uChannel /8; // 0..8 @@ -415,17 +449,28 @@ size_t LoraWanCFList(uint8_t * CFList, size_t uLen) { } // Add next 6 bytes - CFList[idx++] = 0x00; //RFU + CFList[idx++] = 0x00; // RFU CFList[idx++] = 0x00; - CFList[idx++] = 0x00; //RFU + CFList[idx++] = 0x00; // RFU CFList[idx++] = 0x00; CFList[idx++] = 0x00; - CFList[idx++] = 0x01; //CFListType + CFList[idx++] = 0x01; // CFListType break; } default: { // TAS_LORA_REGION_EU868 +/* + uint32_t frequency = (Lora->settings.frequency * 10000); // 868.1 -> 8681000 + + // Add first 15 bytes + for (uint32_t i = 0; i < 5; i++) { + CFList[idx++] = frequency; + CFList[idx++] = frequency >> 8; + CFList[idx++] = frequency >> 16; + } + CFList[idx++] = 0x00; // CFListType +*/ } } return idx; @@ -551,8 +596,9 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { 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 + // 0007010000004140A8D64A89710E4140A82893A8AD137F - Dragino LDS02 + // 0000010000004140A889AD5C17544140A849E1B2CAC27B - Dragino LHT52 + // 000600000000161600B51F000000161600FDA5D8127912 - MerryIoT DW10 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) | @@ -569,8 +615,8 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { 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); + 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; @@ -583,6 +629,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { 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; @@ -621,8 +668,9 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) { EncData[0] = join_data[0]; LoraWanEncryptJoinAccept(Lora->settings.end_node[node].AppKey, &join_data[1], join_data_index-1, &EncData[1]); - // 203106E5000000412E010003017CB31DD4 - Dragino - // 203206E5000000422E010003016A210EEA - MerryIoT + // 203106E5000000412E010003017CB31DD4 - Dragino LDS02 + // 2026B4E06C390AFA1B166D465987F31EC4 - Dragino LHT52 + // 203206E5000000422E010003016A210EEA - MerryIoT DW10 LoraWanSendResponse(EncData, join_data_index, TAS_LORAWAN_JOIN_ACCEPT_DELAY1); result = true; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_73_9_lora.ino b/tasmota/tasmota_xdrv_driver/xdrv_73_9_lora.ino index 50cab344c..80c1b5d19 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_73_9_lora.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_73_9_lora.ino @@ -46,7 +46,7 @@ void LoraSettings2Json(bool show = false) { ResponseAppend_P(PSTR("\"" D_JSON_REGION "\":%d"), Lora->settings.region); // enum 0 = EU868, 1 = AU915 } #ifdef USE_LORAWAN_BRIDGE - if (show && (Lora->settings.region == TAS_LORA_REGION_AU915)) { + if (show && (bitRead(Lora->settings.flags, TAS_LORA_FLAG_BRIDGE_ENABLED))) { LoRaWanRadioInfo_t Rx1Info; LoraWanRadioInfo(TAS_LORAWAN_RADIO_RX1, &Rx1Info, LoraWanFrequencyToChannel()); // Get Rx1Info with values used for RX1 transmit window. (Region specific, calculated from Uplink radio settings) LoRaWanRadioInfo_t Rx2Info; @@ -193,8 +193,8 @@ void LoraSettingsSave(void) { bool LoraSend(uint8_t* data, uint32_t len, bool invert) { uint32_t lora_time = millis(); // Time is important for LoRaWan RX windows bool result = Lora->Send(data, len, invert); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Send (%u) '%*_H', Invert %d, Time %d"), - lora_time, len, data, invert, TimePassedSince(lora_time)); + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Send (%u) '%*_H', Invert %d, Freq %1_f, BW %1_f, SF %d, Time %d"), + lora_time, len, data, invert, &Lora->settings.frequency, &Lora->settings.bandwidth, Lora->settings.spreading_factor, TimePassedSince(lora_time)); return result; } @@ -204,8 +204,8 @@ void LoraInput(void) { char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 }; int packet_size = Lora->Receive(data); if (!packet_size) { return; } - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Rcvd (%u) '%*_H', RSSI %1_f, SNR %1_f"), - Lora->receive_time, packet_size, data, &Lora->rssi, &Lora->snr); + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Rcvd (%u) '%*_H', Freq %1_f, BW %1_f, SF %d, RSSI %1_f, SNR %1_f"), + Lora->receive_time, packet_size, data, &Lora->settings.frequency, &Lora->settings.bandwidth, Lora->settings.spreading_factor, &Lora->rssi, &Lora->snr); #ifdef USE_LORAWAN_BRIDGE if (bitRead(Lora->settings.flags, TAS_LORA_FLAG_BRIDGE_ENABLED)) { if (LoraWanInput((uint8_t*)data, packet_size)) { @@ -457,9 +457,9 @@ void CmndLoraConfig(void) { LoraDefaults(region); // Default region LoRa values break; #ifdef USE_LORAWAN_BRIDGE - case 2: + case 2: { switch (region) { - case TAS_LORA_REGION_AU915: + case TAS_LORA_REGION_AU915: { uint32_t parm[2] = { 0, 0 }; ParseParameters(2, parm); // parm[1] will hold channel Lora->settings.region = region; // Need valid region for LoraWanRadioInfo() @@ -467,21 +467,23 @@ void CmndLoraConfig(void) { LoraWanRadioInfo(TAS_LORAWAN_RADIO_UPLINK, &UpInfo, parm[1]); // Set uplink frequency based on channel Lora->settings.frequency = UpInfo.frequency; break; -// default: -// not implemented + } + default: // TAS_LORA_REGION_EU868 + Lora->settings.frequency = TAS_LORAWAN_FREQUENCY; } LoraWanDefaults(region); // Default region LoRaWan values break; + } #endif // USE_LORAWAN_BRIDGE } - Lora->Config(); + Lora->Config(true); } else { JsonParser parser(XdrvMailbox.data); JsonParserObject root = parser.getRootObject(); if (root) { LoraJson2Settings(root); - Lora->Config(); + Lora->Config(true); } } uint8_t data[1] = { 0 }; @@ -507,6 +509,9 @@ bool Xdrv73(uint32_t function) { switch (function) { case FUNC_LOOP: case FUNC_SLEEP_LOOP: +#ifdef USE_LORAWAN_BRIDGE + LoRaWanSend(); +#endif // USE_LORAWAN_BRIDGE LoraInput(); break; case FUNC_RESET_SETTINGS: