# DD3-LoRa-Bridge-MultiSender Unified firmware for LilyGO T3 v1.6.1 (ESP32 + SX1276 + SSD1306) running as either: - `Sender`: reads energy-only values from multiple IEC 62056-21 meters and sends binary batches over LoRa. - `Receiver`: accepts batches, ACKs with optional time, publishes to MQTT/web. ## Protocol (minimal) Frame format: `[msg_kind:1][dev_id_short:2][payload...][crc16:2]` `msg_kind`: - `0`: `BATCH_UP` (Sender -> Receiver) - `1`: `ACK_DOWN` (Receiver -> Sender) Removed from protocol: - protocol version field - payload type field - MeterData JSON/compressed LoRa path - standalone TimeSync packets CRC16 validation is still required on every frame. ## Payloads ### 1) `BATCH_UP` - Uses existing binary batch/chunk transport. - `sample_count == 0` is valid and means `SYNC_REQUEST`. - Payload starts with `schema_id`: - `0`: legacy schema (kept for compatibility) - `1`: `EnergyMulti` schema (ts + integer kWh for up to 3 meters) ### 2) `ACK_DOWN` (7 bytes) - `flags` (`u8`): bit0 = `time_valid` - `batch_id` (`u16`, big-endian) - `epoch_utc` (`u32`, big-endian) Receiver sets: - `time_valid=1` only when receiver time is authoritative and sane. - Otherwise `time_valid=0` and `epoch_utc=0`. ## Time bootstrap safety Sender starts with: - `g_time_acquired=false` - no real sampling/batching - periodic `SYNC_REQUEST` every `SYNC_REQUEST_INTERVAL_MS` (default `15000ms`) Sender only accepts time from `ACK_DOWN` if: - `time_valid == 1` - `epoch_utc >= 2026-02-01 00:00:00 UTC` (`MIN_ACCEPTED_EPOCH_UTC = 1769904000`) Only then: - system time is set - `g_time_acquired=true` - normal 1 Hz sampling + batch transmit starts This guarantees no pre-`2026-02-01` epoch reaches MQTT or SD/DB paths. ## Multi-meter sender mode - Meter protocol: IEC 62056-21 ASCII Mode D - UART framing: `9600 7E1` - Extracted OBIS: only total energy `1-0:1.8.0` - Conversion: kWh is sent as integer using `floor` (`12345.67 -> 12345`) ### Meter count by build mode - Debug builds (`SERIAL_DEBUG_MODE=1`): `METER_COUNT=2` - keeps USB serial logs on UART0 - uses UART1 + UART2 for meters - Prod builds (`SERIAL_DEBUG_MODE=0`): `METER_COUNT=3` - no serial logging - reclaims UART0 for meter 3 RX ### Meter RX pins (TTGO T3 v1.6.1 defaults) - `PIN_METER1_RX = GPIO34` (UART2 RX) - `PIN_METER2_RX = GPIO25` (UART1 RX) - `PIN_METER3_RX = GPIO3` (UART0 RX, prod only) All pins are configurable in `include/config.h`. ## Receiver MQTT output (EnergyMulti) For `schema_id=1`, MQTT payload includes integer fields: - `energy1_kwh` - `energy2_kwh` - `energy3_kwh` (when meter count is 3) ## Receiver behavior On `BATCH_UP`: 1. Decode batch/chunks. 2. Send `ACK_DOWN` immediately. 3. If `sample_count == 0`: treat as `SYNC_REQUEST`, do not publish MQTT/update stats. 4. Else decode and publish samples as normal. ## Sender/Receiver debug logs (`SERIAL_DEBUG_MODE`) Sender: - `sync: request tx batch_id=%u` - `ack: rx ok batch_id=%u time_valid=%u epoch=%lu set=%u` - `ack: timeout batch_id=%u retry=%u` Receiver: - `ack: tx batch_id=%u time_valid=%u epoch=%lu samples=%u` ## Removed hardware dependency DS3231 RTC support was removed: - no RTC files - no RTC init/load/set logic - no `ENABLE_DS3231` flow ## Build ```bash pio run -e lilygo-t3-v1-6-1 pio run -e lilygo-t3-v1-6-1-test pio run -e lilygo-t3-v1-6-1-prod ```