Reduce sender power draw (RX windows + CPU/WiFi/ADC/pins)

- Add LoRa idle/sleep/receive-window helpers and use short RX windows for ACK/time sync

- Schedule sender time-sync windows (fast/slow) and track RX vs sleep time in debug

- Lower sender power (80 MHz CPU, WiFi/BT off, reduced ADC sampling, unused pins pulldown)

- Make SERIAL_DEBUG_MODE a build flag, add prod envs with debug off, and document changes
This commit is contained in:
2026-02-02 21:42:51 +01:00
parent a4d9be1903
commit 8e6c64a18e
8 changed files with 139 additions and 41 deletions

View File

@@ -110,10 +110,24 @@ bool lora_receive(LoraPacket &pkt, uint32_t timeout_ms) {
}
}
void lora_idle() {
LoRa.idle();
}
void lora_sleep() {
LoRa.sleep();
}
bool lora_receive_window(LoraPacket &pkt, uint32_t timeout_ms) {
if (timeout_ms == 0) {
return false;
}
LoRa.receive();
bool got = lora_receive(pkt, timeout_ms);
LoRa.sleep();
return got;
}
uint32_t lora_airtime_ms(size_t packet_len) {
if (packet_len == 0) {
return 0;

View File

@@ -88,6 +88,10 @@ static uint8_t g_inflight_count = 0;
static uint16_t g_inflight_batch_id = 0;
static bool g_inflight_active = false;
static uint32_t g_last_debug_log_ms = 0;
static uint32_t g_sender_last_timesync_check_ms = 0;
static uint32_t g_sender_rx_window_ms = 0;
static uint32_t g_sender_sleep_ms = 0;
static uint32_t g_sender_power_log_ms = 0;
static void watchdog_kick();
@@ -144,6 +148,22 @@ static void update_battery_cache() {
g_last_battery_ms = millis();
}
static bool sender_timesync_window_due() {
uint32_t interval_sec = SENDER_TIMESYNC_CHECK_SEC_FAST;
if (time_is_synced() && time_rtc_present()) {
interval_sec = SENDER_TIMESYNC_CHECK_SEC_SLOW;
}
if (g_sender_last_timesync_check_ms == 0) {
g_sender_last_timesync_check_ms = millis() - interval_sec * 1000UL;
}
uint32_t now_ms = millis();
if (now_ms - g_sender_last_timesync_check_ms >= interval_sec * 1000UL) {
g_sender_last_timesync_check_ms = now_ms;
return true;
}
return false;
}
static bool batch_queue_drop_oldest() {
if (g_batch_count == 0) {
return false;
@@ -620,6 +640,7 @@ void setup() {
if (g_role == DeviceRole::Sender) {
power_sender_init();
power_configure_unused_pins_sender();
meter_init();
g_last_sample_ms = millis() - METER_SAMPLE_INTERVAL_MS;
g_last_send_ms = millis();
@@ -712,37 +733,18 @@ static void sender_loop() {
}
if (g_batch_ack_pending) {
uint32_t end_ms = millis() + 400;
while (millis() < end_ms) {
LoraPacket ack_pkt = {};
if (!lora_receive(ack_pkt, 0) || ack_pkt.protocol_version != PROTOCOL_VERSION) {
delay(5);
continue;
}
if (ack_pkt.payload_type == PayloadType::Ack && ack_pkt.payload_len >= 6 && ack_pkt.role == DeviceRole::Receiver) {
uint16_t ack_id = read_u16_le(ack_pkt.payload);
uint16_t ack_sender = read_u16_le(&ack_pkt.payload[2]);
uint16_t ack_receiver = read_u16_le(&ack_pkt.payload[4]);
if (ack_sender == g_short_id && ack_receiver == ack_pkt.device_id_short &&
g_batch_ack_pending && ack_id == g_last_sent_batch_id) {
g_last_acked_batch_id = ack_id;
serial_debug_printf("ack: ok batch_id=%u", ack_id);
finish_inflight_batch();
break;
}
}
LoraPacket ack_pkt = {};
uint32_t rx_start = millis();
bool got_ack = lora_receive_window(ack_pkt, 400);
uint32_t rx_elapsed = millis() - rx_start;
if (SERIAL_DEBUG_MODE) {
g_sender_rx_window_ms += rx_elapsed;
}
}
LoraPacket rx = {};
if (lora_receive(rx, 0) && rx.protocol_version == PROTOCOL_VERSION) {
if (rx.payload_type == PayloadType::TimeSync) {
time_handle_timesync_payload(rx.payload, rx.payload_len);
} else if (rx.payload_type == PayloadType::Ack && rx.payload_len >= 6 && rx.role == DeviceRole::Receiver) {
uint16_t ack_id = read_u16_le(rx.payload);
uint16_t ack_sender = read_u16_le(&rx.payload[2]);
uint16_t ack_receiver = read_u16_le(&rx.payload[4]);
if (ack_sender == g_short_id && ack_receiver == rx.device_id_short &&
if (got_ack && ack_pkt.payload_type == PayloadType::Ack && ack_pkt.payload_len >= 6 && ack_pkt.role == DeviceRole::Receiver) {
uint16_t ack_id = read_u16_le(ack_pkt.payload);
uint16_t ack_sender = read_u16_le(&ack_pkt.payload[2]);
uint16_t ack_receiver = read_u16_le(&ack_pkt.payload[4]);
if (ack_sender == g_short_id && ack_receiver == ack_pkt.device_id_short &&
g_batch_ack_pending && ack_id == g_last_sent_batch_id) {
g_last_acked_batch_id = ack_id;
serial_debug_printf("ack: ok batch_id=%u", ack_id);
@@ -751,6 +753,23 @@ static void sender_loop() {
}
}
bool timesync_due = (!g_batch_ack_pending && sender_timesync_window_due());
if (timesync_due) {
LoraPacket rx = {};
uint32_t rx_start = millis();
bool got = lora_receive_window(rx, SENDER_TIMESYNC_WINDOW_MS);
uint32_t rx_elapsed = millis() - rx_start;
if (SERIAL_DEBUG_MODE) {
g_sender_rx_window_ms += rx_elapsed;
}
if (got && rx.payload_type == PayloadType::TimeSync) {
time_handle_timesync_payload(rx.payload, rx.payload_len);
}
}
if (!g_batch_ack_pending) {
lora_sleep();
}
if (g_batch_ack_pending && (now_ms - g_last_batch_send_ms >= g_batch_ack_timeout_ms)) {
if (g_batch_retry_count < BATCH_MAX_RETRIES) {
g_batch_retry_count++;
@@ -780,6 +799,15 @@ static void sender_loop() {
uint32_t next_due = next_sample_due < next_send_due ? next_sample_due : next_send_due;
if (!g_batch_ack_pending && next_due > now_ms) {
watchdog_kick();
if (SERIAL_DEBUG_MODE) {
g_sender_sleep_ms += (next_due - now_ms);
if (now_ms - g_sender_power_log_ms >= 10000) {
g_sender_power_log_ms = now_ms;
serial_debug_printf("power: rx_ms=%lu sleep_ms=%lu", static_cast<unsigned long>(g_sender_rx_window_ms),
static_cast<unsigned long>(g_sender_sleep_ms));
}
}
lora_sleep();
light_sleep_ms(next_due - now_ms);
}
}

View File

@@ -10,7 +10,10 @@ static constexpr float BATTERY_CAL = 1.0f;
static constexpr float ADC_REF_V = 3.3f;
void power_sender_init() {
setCpuFrequencyMhz(80);
WiFi.mode(WIFI_OFF);
esp_wifi_stop();
esp_wifi_deinit();
btStop();
analogReadResolution(12);
pinMode(PIN_BAT_ADC, INPUT);
@@ -22,14 +25,19 @@ void power_receiver_init() {
pinMode(PIN_BAT_ADC, INPUT);
}
void read_battery(MeterData &data) {
const int samples = 8;
uint32_t sum = 0;
for (int i = 0; i < samples; ++i) {
sum += analogRead(PIN_BAT_ADC);
delay(5);
void power_configure_unused_pins_sender() {
// Board-specific: only touch pins that are known unused and safe on TTGO LoRa32 v1.6.1
const uint8_t pins[] = {32, 33};
for (uint8_t pin : pins) {
pinMode(pin, INPUT_PULLDOWN);
}
float avg = static_cast<float>(sum) / samples;
}
void read_battery(MeterData &data) {
uint32_t sum = 0;
sum += analogRead(PIN_BAT_ADC);
sum += analogRead(PIN_BAT_ADC);
float avg = static_cast<float>(sum) / 2.0f;
float v = (avg / 4095.0f) * ADC_REF_V * BATTERY_DIVIDER * BATTERY_CAL;
data.battery_voltage_v = v;