refactor lora payload timing

Bump the batch payload codec to schema v4 with separate meter-time and UTC anchors, then use meter seconds for sparse batch slotting and receiver reconstruction.

Update the current 868 MHz bench configuration, allow ACKs from configured receiver short IDs, improve AP-to-STA recovery, quiet the test build, and document the changed protocol in the README.
This commit is contained in:
2026-06-30 12:19:27 +02:00
parent 664ff1d744
commit def09160d0
13 changed files with 242 additions and 133 deletions
+16 -14
View File
@@ -9,13 +9,14 @@ Firmware for LilyGO T3 v1.6.1 (`ESP32 + SX1276 + SSD1306`) that runs in two role
- Single codebase, role selected at boot by `detect_role()` (`src/config.cpp`).
- LoRa transport is wrapped with firmware-level CRC16-CCITT (`src/lora_transport.cpp`).
- Sender meter ingest is decoupled from LoRa waits via FreeRTOS meter reader task + queue on ESP32 (`src/sender_state_machine.cpp`).
- Batch payload codec is schema `v3` with a 30-bit `present_mask` over `[t_last-29, t_last]` (`lib/dd3_legacy_core/src/payload_codec.cpp`).
- Batch payload codec is schema `v4` with a 30-bit `present_mask` over `[meter_t_last-29, meter_t_last]` and a separate UTC anchor (`lib/dd3_legacy_core/src/payload_codec.cpp`).
- Sender retries reuse cached encoded payload bytes (no re-encode on retry path).
- Sender ACK receive windows adapt from observed ACK RTT + miss streak.
- Sender catch-up mode drains backlog with immediate extra sends when more than one batch is queued (still ACK-gated, single inflight batch).
- Sender only starts normal metering/transmit flow after valid time bootstrap from receiver ACK.
- Sender fault counters are reset at first valid time sync and again at each UTC hour boundary.
- Receiver runs STA mode if stored config is valid and connects, otherwise AP fallback.
- Current bench defaults in `include/config.h`: 868 MHz LoRa, sender short-ID `0x6540`, receiver short-ID `0x7EB4`.
## LoRa Protocol
@@ -35,12 +36,14 @@ Transport layer chunks payload into:
Receiver reassembles all chunks before decode.
Payload codec (`schema=3`, magic `0xDDB3`) carries:
- metadata: sender ID, batch ID, `t_last`, `present_mask`, battery mV, error counters
Payload codec (`schema=4`, magic `0xDDB3`) carries:
- metadata: sender ID, batch ID, `meter_t_last`, `ts_utc_last`, `present_mask`, battery mV, error counters
- arrays per present sample: `energy_wh[]`, `p1_w[]`, `p2_w[]`, `p3_w[]`
`n == 0` with `present_mask == 0` is valid and used for sync request packets.
Schema `v4` is not wire-compatible with schema `v3`: both sender and receiver must be flashed with matching firmware.
### AckDown (7 bytes payload)
`[flags:1][batch_id_be:2][epoch_utc_be:4]`
@@ -90,7 +93,8 @@ Timestamp derivation:
- sample epoch: `ts_utc = meter_seconds + epoch_offset`
- jump checks: rollback, wall-time delta mismatch, anchor drift
Sender builds sparse 30-slot windows and sends every `METER_SEND_INTERVAL_MS` (`30s`).
Sender builds sparse 30-slot windows in meter-seconds space and sends every `METER_SEND_INTERVAL_MS` (`30s`).
Samples without a valid meter seconds value are rejected for normal batch transmission.
When backlog is present (`batch_q > 1`), sender transmits additional queued batches immediately after ACK to reduce lag, while keeping stop-and-wait ACK semantics.
Sender diagnostics (serial debug mode):
@@ -111,11 +115,13 @@ For decoded `BatchUp`:
5. Track duplicates per configured sender.
6. If duplicate: update duplicate counters/time, skip data write/publish.
7. If `n==0`: sync request path only.
8. Else reconstruct each sample timestamp from `t_last + present_mask`, then:
8. Else reconstruct each sample from `meter_t_last + present_mask` and `ts_utc_last + present_mask`, then:
- append to SD CSV
- publish MQTT state
- update web status and last batch table
ACK validation accepts only configured sender IDs, the sender's own short-ID, or configured receiver short-IDs (`EXPECTED_RECEIVER_IDS`) so receiver-originated ACKs are allowed without accepting arbitrary device IDs.
## MQTT
State topic:
@@ -154,6 +160,7 @@ Home Assistant discovery:
- Default web credentials: `admin/admin`.
- AP auth requirement is controlled by `WEB_AUTH_REQUIRE_AP` (default `true`).
- STA auth requirement is controlled by `WEB_AUTH_REQUIRE_STA` (default `true`).
- If the receiver boots into AP mode with saved Wi-Fi and MQTT config, it periodically retries STA mode and reinitializes NTP, MQTT, and the web server after a successful reconnect.
Web timestamp display:
- human-facing timestamps show `epoch (HH:MM:SS TZ)` in local configured timezone.
@@ -176,19 +183,14 @@ OLED duplicate display:
## Build Environments
From `platformio.ini`:
- `lilygo-t3-v1-6-1`
- `lilygo-t3-v1-6-1-test`
- `lilygo-t3-v1-6-1-868`
- `lilygo-t3-v1-6-1-868-test`
- `lilygo-t3-v1-6-1-payload-test`
- `lilygo-t3-v1-6-1-868-payload-test`
- `lilygo-t3-v1-6-1-prod`
- `lilygo-t3-v1-6-1-868-prod`
- `production`: serial debug off, light sleep on
- `debug`: serial diagnostics on, real meter and real LoRa
- `test`: synthetic meter samples and payload codec self-test, serial debug off
Example:
```bash
python -m platformio run -e lilygo-t3-v1-6-1
python -m platformio run -e production
```
## Test Mode