From 50436cd0bb2187d9f27681481b32b63b42af5a49 Mon Sep 17 00:00:00 2001 From: acidburns Date: Sun, 1 Feb 2026 20:59:45 +0100 Subject: [PATCH] document batching updates and restore bat_v in batches --- README.md | 20 +++++++++++++------- include/config.h | 4 ++-- src/json_codec.cpp | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 300b8c8..cda4709 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ void sender_loop() { read_battery(data); // VBAT + SoC if (time_to_send_batch()) { - json = meterBatchToJson(samples, batch_id); + json = meterBatchToJson(samples, batch_id); // bat_v per batch, t_first/t_last included compressed = compressBuffer(json); lora_send(packet(MeterBatch, compressed)); } @@ -92,7 +92,7 @@ bool lora_send(const LoraPacket &pkt); // add header + CRC16 and transmit - Web UI: - AP mode: status + WiFi/MQTT config. - STA mode: status + per-sender pages. -- OLED cycles through receiver status and per-sender pages. +- OLED cycles through receiver status and per-sender pages (receiver OLED never sleeps). **Receiver loop (pseudo-code)**: ```cpp @@ -106,7 +106,7 @@ void receiver_loop() { } } else if (pkt.type == MeterBatch) { json = reassemble_and_decompress_batch(pkt); - for (sample in jsonToMeterBatch(json)) { + for (sample in jsonToMeterBatch(json)) { // uses t_first/t_last for jittered timestamps update_sender_status(sample); mqtt_publish_state(sample); } @@ -176,7 +176,7 @@ Packet layout: LoRa radio settings: - Frequency: **433 MHz** or **868 MHz** (set by build env via `LORA_FREQUENCY_HZ`) -- SF12, BW 125 kHz, CR 4/5, CRC on, Sync Word 0x34 +- SF10, BW 125 kHz, CR 4/5, CRC on, Sync Word 0x34 ## Data Format JSON payload (sender + MQTT): @@ -203,6 +203,8 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units "sender": "s01", "batch_id": 1842, "t0": 1738288000, + "t_first": 1738288000, + "t_last": 1738288030, "dt_s": 1, "n": 3, "energy_wh": [123456700, 123456701, 123456701], @@ -210,6 +212,7 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units "p1_w": [480, 490, 500], "p2_w": [450, 450, 450], "p3_w": [0, 0, 0], + "bat_v": 3.92, "meta": { "rssi": -92, "snr": 7.5, @@ -221,6 +224,7 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units Notes: - `sender` maps to `EXPECTED_SENDER_IDS` order (`s01` = first sender). - `meta` is injected by the receiver after batch reassembly. +- `bat_v` is a single batch-level value (percent is calculated locally). ## Device IDs - Derived from WiFi STA MAC. @@ -236,9 +240,7 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C }; ## OLED Behavior - Sender: OLED stays **ON for 10 seconds** on each wake, then powers down for sleep. -- Receiver: OLED follows the 10-minute auto-off behavior: - - GPIO14 HIGH: OLED forced ON. - - GPIO14 LOW: auto-off after 10 minutes. +- Receiver: OLED is always on (no auto-off). - Pages rotate every 4s. ## Power & Battery @@ -283,6 +285,10 @@ Key timing settings in `include/config.h`: - `METER_SEND_INTERVAL_MS` - `BATCH_ACK_TIMEOUT_MS` - `BATCH_MAX_RETRIES` + - `BATCH_QUEUE_DEPTH` + - `BATCH_RETRY_POLICY` (keep or drop on retry exhaustion) + - `SERIAL_DEBUG_MODE` / `SERIAL_DEBUG_DUMP_JSON` + - `LORA_SEND_BYPASS` (debug only) ## Limits & Known Constraints - **Compression**: uses lightweight RLE (good for JSON but not optimal). diff --git a/include/config.h b/include/config.h index e977193..c9e8fc9 100644 --- a/include/config.h +++ b/include/config.h @@ -73,8 +73,8 @@ constexpr uint8_t BATCH_QUEUE_DEPTH = 10; constexpr BatchRetryPolicy BATCH_RETRY_POLICY = BatchRetryPolicy::Keep; constexpr uint32_t WATCHDOG_TIMEOUT_SEC = 120; constexpr bool ENABLE_HA_DISCOVERY = true; -constexpr bool SERIAL_DEBUG_MODE = true; -constexpr bool SERIAL_DEBUG_DUMP_JSON = true; +constexpr bool SERIAL_DEBUG_MODE = false; +constexpr bool SERIAL_DEBUG_DUMP_JSON = false; constexpr bool LORA_SEND_BYPASS = false; constexpr uint8_t NUM_SENDERS = 1; diff --git a/src/json_codec.cpp b/src/json_codec.cpp index 1f369e7..24a5017 100644 --- a/src/json_codec.cpp +++ b/src/json_codec.cpp @@ -199,6 +199,11 @@ bool meterBatchToJson(const MeterData *samples, size_t count, uint16_t batch_id, if (last_error != FaultType::None) { doc["err_last"] = static_cast(last_error); } + if (!isnan(samples[count - 1].battery_voltage_v)) { + char bat_buf[16]; + format_float_2(bat_buf, sizeof(bat_buf), samples[count - 1].battery_voltage_v); + doc["bat_v"] = serialized(bat_buf); + } JsonArray energy = doc.createNestedArray("energy_wh"); JsonArray p_w = doc.createNestedArray("p_w"); @@ -235,6 +240,7 @@ bool jsonToMeterBatch(const String &json, MeterData *out_samples, size_t max_cou uint32_t err_m = doc["err_m"] | 0; uint32_t err_tx = doc["err_tx"] | 0; FaultType last_error = static_cast(doc["err_last"] | 0); + float bat_v = doc["bat_v"] | NAN; if (!doc["schema"].isNull()) { if ((doc["schema"] | 0) != 1) { @@ -284,8 +290,12 @@ bool jsonToMeterBatch(const String &json, MeterData *out_samples, size_t max_cou data.phase_power_w[0] = static_cast(p1_w[idx] | 0); data.phase_power_w[1] = static_cast(p2_w[idx] | 0); data.phase_power_w[2] = static_cast(p3_w[idx] | 0); - data.battery_voltage_v = NAN; - data.battery_percent = 0; + data.battery_voltage_v = bat_v; + if (!isnan(bat_v)) { + data.battery_percent = battery_percent_from_voltage(bat_v); + } else { + data.battery_percent = 0; + } data.valid = true; data.link_valid = false; data.link_rssi_dbm = 0;