From 6bc3ba7405e138f253b79f049531c21c7d071414 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 5 May 2026 21:56:00 +0200 Subject: [PATCH] disable default duty cycling + SX driver fixes --- zephcore/Kconfig | 9 +-- zephcore/app/RepeaterDataStore.cpp | 13 ++-- zephcore/boards/common/zephcore_common.conf | 4 +- .../zephyr/0003-lora-sx126x-native.patch | 64 ++++++++++++------- zephcore/src/main_companion.cpp | 2 +- 5 files changed, 57 insertions(+), 35 deletions(-) diff --git a/zephcore/Kconfig b/zephcore/Kconfig index c9026a2..639ce62 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -393,17 +393,18 @@ menu "LoRa Power Saving" config ZEPHCORE_LORA_RX_DUTY_CYCLE bool "Enable LoRa RX duty cycle power saving" - default y if (ZEPHCORE_ROLE_COMPANION || ZEPHCORE_ROLE_REPEATER) && !ZEPHCORE_RADIO_LR1110 && !ZEPHCORE_RADIO_LR2021 && !ZEPHCORE_RADIO_SX127X default n help Enable RX duty cycling for power saving on battery-powered devices. Uses RadioLib's algorithm to calculate optimal timing based on SF/BW. - Auto-enabled for companion and repeater devices with SX1262/LLCC68 - radios. Duty cycle mode also resets AGC state after every received + Disabled by default for all roles. Enable manually if you want + power savings and have verified acceptable packet loss/latency for + your environment and radio. + Duty cycle mode also resets AGC state after every received packet via Calibrate(ALL), preventing the "near-far" gain-lock problem where a strong signal desensitises the receiver. - Disabled for LR1110 (cannot lock on mid-preamble, causes packet + Still disabled for LR1110 (cannot lock on mid-preamble, causes packet loss) and LR2021 (untested — enable manually after verifying no packet loss). Can be toggled at runtime via CLI. diff --git a/zephcore/app/RepeaterDataStore.cpp b/zephcore/app/RepeaterDataStore.cpp index 1e266a4..d5ad465 100644 --- a/zephcore/app/RepeaterDataStore.cpp +++ b/zephcore/app/RepeaterDataStore.cpp @@ -137,9 +137,6 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) { prefs.flood_advert_interval = 25; prefs.loop_detect = LOOP_DETECT_MINIMAL; prefs.path_hash_mode = 1; -#if IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE) - prefs.rx_duty_cycle = 1; -#endif /* Persist defaults so flash always has a prefs file from boot 1. * Lets later code (e.g. tempradio revert) trust that flash is * authoritative without a "first run" special case. */ @@ -237,12 +234,16 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) { prefs.rx_boost = 1; prefs.path_hash_mode = 1; prefs.loop_detect = LOOP_DETECT_MINIMAL; -#if IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE) - prefs.rx_duty_cycle = 1; -#endif savePrefs(prefs); LOG_INF("loadPrefs: upgraded prefs format (%d -> 294 bytes)", (int)entry.size); } + + /* One-time migration: disable RX duty cycle if it was previously enabled. */ + if (prefs.rx_duty_cycle != 0) { + prefs.rx_duty_cycle = 0; + savePrefs(prefs); + LOG_INF("loadPrefs: migrated rx_duty_cycle to disabled"); + } return true; } diff --git a/zephcore/boards/common/zephcore_common.conf b/zephcore/boards/common/zephcore_common.conf index 3e10932..a657625 100644 --- a/zephcore/boards/common/zephcore_common.conf +++ b/zephcore/boards/common/zephcore_common.conf @@ -195,8 +195,8 @@ CONFIG_POWEROFF=y # ========== LoRa Power Saving ========== # RX duty cycle defaults are in Kconfig: -# ON for companion + SX1262/LLCC68 (safe, saves 60-70% RX current) -# OFF for repeaters (latency > battery) and LR1110 (mid-preamble bug) +# OFF for all roles by default +# LR1110 remains explicitly unsupported (mid-preamble lock bug) # Override at runtime via CLI "set rxduty on/off". # ========== ZephCore Log Levels ========== diff --git a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch index 3fde6f0..6c03b99 100644 --- a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch +++ b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch @@ -68,7 +68,7 @@ index 17689720dd2..09982dc2e70 100644 return -EINVAL; } diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c -index 30243ba5dc7..8611d76a782 100644 +index 30243ba5dc7..befb84797eb 100644 --- a/drivers/lora/native/sx126x/sx126x.c +++ b/drivers/lora/native/sx126x/sx126x.c @@ -10,10 +10,16 @@ @@ -201,7 +201,27 @@ index 30243ba5dc7..8611d76a782 100644 /* Set packet type to LoRa */ ret = sx126x_set_packet_type(dev, SX126X_PACKET_TYPE_LORA); if (ret < 0) { -@@ -398,7 +439,7 @@ static void sx126x_dio1_callback(const struct device *dev) +@@ -374,10 +415,16 @@ static int sx126x_chip_init(const struct device *dev) + LOG_ERR("Set packet type failed: %d", ret); + return ret; + } + +- /* Configure IRQs on DIO1: TX done, RX done, timeout */ ++ /* Global mask includes preamble/header so the status register bits are ++ * set (enabling sx126x_is_receiving() polling); DIO1 excludes them to ++ * avoid spurious work-queue wakeups on every detected preamble. */ + uint16_t irq_mask = SX126X_IRQ_TX_DONE | SX126X_IRQ_RX_DONE | +- SX126X_IRQ_RX_TX_TIMEOUT | SX126X_IRQ_CRC_ERR; +- ret = sx126x_set_dio_irq_params(dev, irq_mask, irq_mask, 0, 0); ++ SX126X_IRQ_RX_TX_TIMEOUT | SX126X_IRQ_CRC_ERR | ++ SX126X_IRQ_CAD_DONE | SX126X_IRQ_CAD_ACTIVITY_DETECTED | ++ SX126X_IRQ_PREAMBLE_DETECTED | SX126X_IRQ_HEADER_VALID; ++ uint16_t dio1_mask = irq_mask & ++ ~(SX126X_IRQ_PREAMBLE_DETECTED | SX126X_IRQ_HEADER_VALID); ++ ret = sx126x_set_dio_irq_params(dev, irq_mask, dio1_mask, 0, 0); + if (ret < 0) { + LOG_ERR("Set IRQ params failed: %d", ret); +@@ -398,7 +445,7 @@ static void sx126x_dio1_callback(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -210,7 +230,7 @@ index 30243ba5dc7..8611d76a782 100644 } static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx) -@@ -406,7 +447,20 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx) +@@ -406,7 +453,20 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx) const struct sx126x_hal_config *config = dev->config; sx126x_hal_set_antenna_enable(dev, enable); @@ -232,7 +252,7 @@ index 30243ba5dc7..8611d76a782 100644 sx126x_hal_set_rf_switch(dev, enable, tx); } } -@@ -455,6 +509,80 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev) +@@ -455,6 +515,80 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev) } #endif /* CONFIG_PM_DEVICE */ @@ -313,7 +333,7 @@ index 30243ba5dc7..8611d76a782 100644 static int sx126x_set_sleep(const struct device *dev) { struct sx126x_data *data = dev->data; -@@ -533,6 +661,9 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta +@@ -533,6 +667,9 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta struct sx126x_data *data = dev->data; struct sx126x_rx_result result = { 0 }; uint8_t payload_len = 0, offset = 0; @@ -323,7 +343,7 @@ index 30243ba5dc7..8611d76a782 100644 int ret; /* Get received packet info */ -@@ -564,20 +695,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta +@@ -564,20 +701,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta } } @@ -370,7 +390,7 @@ index 30243ba5dc7..8611d76a782 100644 } } else { /* Sync mode */ -@@ -589,8 +734,46 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta +@@ -589,8 +740,46 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta static void sx126x_handle_irq_timeout(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -417,7 +437,7 @@ index 30243ba5dc7..8611d76a782 100644 sx126x_set_sleep(dev); if (data->tx_async_signal != NULL) { -@@ -637,6 +820,25 @@ static void sx126x_irq_work_handler(struct k_work *work) +@@ -637,6 +826,25 @@ static void sx126x_irq_work_handler(struct k_work *work) sx126x_handle_irq_timeout(dev); } @@ -443,7 +463,7 @@ index 30243ba5dc7..8611d76a782 100644 /* Re-enable the DIO1 interrupt for the next event (unless sleeping) */ if (atomic_get(&data->state) != SX126X_REST_STATE) { sx126x_hal_dio1_irq_enable(dev); -@@ -682,7 +884,7 @@ static int sx126x_lora_config(const struct device *dev, +@@ -682,7 +890,7 @@ static int sx126x_lora_config(const struct device *dev, /* Configure PA and TX power based on chip variant and frequency */ ret = sx126x_hal_configure_tx_params(dev, config->tx_power, config->frequency, @@ -452,7 +472,7 @@ index 30243ba5dc7..8611d76a782 100644 if (ret < 0) { goto out; } -@@ -698,6 +900,29 @@ static int sx126x_lora_config(const struct device *dev, +@@ -698,6 +906,29 @@ static int sx126x_lora_config(const struct device *dev, goto out; } @@ -482,7 +502,7 @@ index 30243ba5dc7..8611d76a782 100644 /* Set sync word */ ret = sx126x_set_sync_word(dev, config->public_network); if (ret < 0) { -@@ -721,6 +946,8 @@ out: +@@ -721,6 +952,8 @@ out: return ret; } @@ -491,7 +511,7 @@ index 30243ba5dc7..8611d76a782 100644 static int sx126x_lora_send_async(const struct device *dev, uint8_t *data_buf, uint32_t data_len, struct k_poll_signal *async) -@@ -752,6 +979,65 @@ static int sx126x_lora_send_async(const struct device *dev, +@@ -752,6 +985,65 @@ static int sx126x_lora_send_async(const struct device *dev, return ret; } @@ -557,7 +577,7 @@ index 30243ba5dc7..8611d76a782 100644 data->tx_async_signal = async; k_msgq_purge(&data->tx_msgq); -@@ -777,6 +1063,29 @@ static int sx126x_lora_send_async(const struct device *dev, +@@ -777,6 +1069,29 @@ static int sx126x_lora_send_async(const struct device *dev, /* Enable antenna and set TX path */ sx126x_set_rf_path(dev, true, true); @@ -587,7 +607,7 @@ index 30243ba5dc7..8611d76a782 100644 /* Start transmission with 10 second timeout */ ret = sx126x_set_tx(dev, 10000); if (ret < 0) { -@@ -847,6 +1156,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf, +@@ -847,6 +1162,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf, } data->rx_cb = NULL; @@ -596,7 +616,7 @@ index 30243ba5dc7..8611d76a782 100644 k_msgq_purge(&data->rx_msgq); /* Set packet parameters for variable length reception */ -@@ -918,6 +1229,7 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -918,6 +1235,7 @@ static int sx126x_lora_recv_async(const struct device *dev, /* Stop async reception */ data->rx_cb = NULL; data->rx_cb_user_data = NULL; @@ -604,7 +624,7 @@ index 30243ba5dc7..8611d76a782 100644 if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) { sx126x_set_standby(dev, SX126X_STANDBY_RC); sx126x_set_sleep(dev); -@@ -947,6 +1259,8 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -947,6 +1265,8 @@ static int sx126x_lora_recv_async(const struct device *dev, data->rx_cb = cb; data->rx_cb_user_data = user_data; @@ -613,7 +633,7 @@ index 30243ba5dc7..8611d76a782 100644 /* Set packet parameters */ ret = sx126x_set_packet_params(dev, -@@ -959,6 +1273,7 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -959,6 +1279,7 @@ static int sx126x_lora_recv_async(const struct device *dev, SX126X_LORA_IQ_INVERTED : SX126X_LORA_IQ_STANDARD); if (ret < 0) { data->rx_cb = NULL; @@ -621,7 +641,7 @@ index 30243ba5dc7..8611d76a782 100644 sx126x_set_sleep(dev); k_mutex_unlock(&data->lock); return ret; -@@ -971,11 +1286,21 @@ static int sx126x_lora_recv_async(const struct device *dev, +@@ -971,11 +1292,21 @@ static int sx126x_lora_recv_async(const struct device *dev, ret = sx126x_set_rx(dev, 0); if (ret < 0) { data->rx_cb = NULL; @@ -643,7 +663,7 @@ index 30243ba5dc7..8611d76a782 100644 k_mutex_unlock(&data->lock); return 0; } -@@ -1051,7 +1376,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, +@@ -1051,7 +1382,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, /* Set PA config and TX power */ ret = sx126x_hal_configure_tx_params(dev, tx_power, frequency, @@ -652,7 +672,7 @@ index 30243ba5dc7..8611d76a782 100644 if (ret < 0) { sx126x_set_sleep(dev); k_mutex_unlock(&data->lock); -@@ -1083,14 +1408,500 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, +@@ -1083,14 +1414,500 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency, return 0; } @@ -1160,7 +1180,7 @@ index 30243ba5dc7..8611d76a782 100644 }; #ifdef CONFIG_PM_DEVICE -@@ -1112,6 +1923,7 @@ static int sx126x_pm_action(const struct device *dev, +@@ -1112,6 +1929,7 @@ static int sx126x_pm_action(const struct device *dev, static int sx126x_init(const struct device *dev) { struct sx126x_data *data = dev->data; @@ -1168,7 +1188,7 @@ index 30243ba5dc7..8611d76a782 100644 int ret; /* Initialize data structures */ -@@ -1121,9 +1933,25 @@ static int sx126x_init(const struct device *dev) +@@ -1121,9 +1939,25 @@ static int sx126x_init(const struct device *dev) k_msgq_init(&data->rx_msgq, (char *)&data->rx_result, sizeof(struct sx126x_rx_result), 1); k_work_init(&data->irq_work, sx126x_irq_work_handler); diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index eeaa664..19fbca9 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -534,7 +534,7 @@ int main(void) companion_mesh.prefs.tx_power_dbm = 22; companion_mesh.prefs.rx_delay_base = 0.0f; /* Disabled for companion */ companion_mesh.prefs.airtime_factor = 9.0f; /* Arduino formula: 100/(af+1) → 10% (EU 868 default) */ - companion_mesh.prefs.rx_duty_cycle = 1; /* Companions: duty cycle ON by default (power save) */ + companion_mesh.prefs.rx_duty_cycle = 0; /* Default OFF: continuous RX */ companion_mesh.prefs.rx_boost = 1; /* Default: boosted RX (+3dB sensitivity, +2mA) */ companion_mesh.prefs.apc_enabled = 0; /* Default: APC off */ companion_mesh.prefs.apc_margin = 20; /* Companions: more conservative margin (mobile) */