Files
DD3-LoRa-Bridge-MultiSender/README.md

4.5 KiB

DD3-LoRa-Bridge-MultiSender

Firmware for LilyGO T3 v1.6.1 (ESP32 + SX1276 + SSD1306) that runs as either:

  • Sender (PIN GPIO14 HIGH): reads one IEC 62056-21 meter, batches samples, sends over LoRa.
  • Receiver (PIN GPIO14 LOW): receives/ACKs batches, publishes MQTT, serves web UI, logs to SD.

Current Architecture

  • Single codebase, role selected at boot via detect_role() (include/config.h, src/config.cpp).
  • LoRa link uses explicit CRC16 frame protection in firmware (src/lora_transport.cpp).
  • Sender batches up to 30 samples and retries on missing ACK (BATCH_MAX_RETRIES=2, retry policy Keep).
  • Sender meter parsing is decoupled from LoRa ACK waits using a dedicated FreeRTOS reader task + queue (src/main.cpp).
  • Receiver uses STA mode when config is valid, otherwise AP fallback with web config.
  • No debug auto-reboot timer is active in normal firmware loops.

LoRa Frame Protocol (Current)

On-air frame format:

[msg_kind:1][device_short_id:2][payload...][crc16:2]

msg_kind:

  • 0 = BatchUp
  • 1 = AckDown

BatchUp

Transport is chunked (batch_id, chunk_index, chunk_count, total_len) and reassembled before payload decode.

Payload codec (src/payload_codec.cpp) currently uses:

  • kMagic=0xDDB3
  • kSchema=2
  • metadata: sender, batch, timestamp, interval, battery, fault counters
  • data arrays: energy_wh[], p1_w[], p2_w[], p3_w[]

n == 0 is valid and used for sync request packets.

AckDown (7 bytes)

[flags:1][batch_id_be:2][epoch_utc_be:4]

  • flags bit0: time_valid
  • Receiver repeats ACK (ACK_REPEAT_COUNT=3, ACK_REPEAT_DELAY_MS=200).
  • Sender accepts time only if time_valid=1 and epoch >= MIN_ACCEPTED_EPOCH_UTC (2026-02-01 00:00:00 UTC).

Time Bootstrap Guardrail

On sender boot:

  • g_time_acquired=false
  • no normal sampling/transmit yet
  • sync request every SYNC_REQUEST_INTERVAL_MS (15s)

Only after valid ACK time is received:

  • system time is set
  • normal 1 Hz sampling and periodic LoRa batch transmit start

This blocks pre-threshold timestamps from MQTT/SD paths.

Timezone handling:

  • Local time rendering uses TIMEZONE_TZ from include/config.h.
  • Default value is CET-1CEST,M3.5.0/2,M10.5.0/3 and can be changed at compile time.

Sender Meter Path

Implemented in src/meter_driver.cpp + sender loop in src/main.cpp:

  • UART: Serial2, RX pin GPIO34 (PIN_METER_RX), 9600 7E1
  • ESP32 RX buffer is enlarged to 8192 bytes to survive long LoRa blocking sections.
  • Frame detection: starts at '/', ends at '!', timeout protection included (METER_FRAME_TIMEOUT_MS=20000).
  • Parsing runs in a dedicated sender task and is handed to the main sender loop via queue.
  • Parsed OBIS values:
    • 1-0:1.8.0 (total energy)
    • 1-0:16.7.0 (total power)
    • 1-0:36.7.0, 56.7.0, 76.7.0 (phase powers)
  • 1-0:1.8.0*Wh is automatically scaled to kWh

Sender samples every second and transmits batches every 30 seconds.

Receiver Behavior

For valid BatchUp decode:

  1. Reassemble chunks and decode payload.
  2. Send AckDown immediately.
  3. Drop duplicate batches per sender (batch_id tracking).
  4. If n==0: treat as sync request only.
  5. Else convert samples, log to SD, update web UI, publish MQTT.

MQTT Topics and Payloads

State topic:

  • smartmeter/<device_id>/state

Fault topic (retained):

  • smartmeter/<device_id>/faults

State JSON fields (src/json_codec.cpp):

  • id, ts, e_kwh
  • p_w, p1_w, p2_w, p3_w
  • bat_v, bat_pct
  • optional link fields: rssi, snr
  • fault/reject fields: err_last, rx_reject, rx_reject_text (+ non-zero counters)

Home Assistant discovery is enabled (ENABLE_HA_DISCOVERY=true) and publishes config topics under:

  • homeassistant/sensor/<device_id>/<key>/config

Web UI, Wi-Fi, Storage

  • Wi-Fi/MQTT/NTP/web-auth config persists in Preferences (wifi_manager).
  • AP fallback SSID prefix: DD3-Bridge-.
  • Default web credentials: admin/admin.
  • SD logging enabled (ENABLE_SD_LOGGING=true).

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

Example:

~/.platformio/penv/bin/pio run -e lilygo-t3-v1-6-1

Test Mode

ENABLE_TEST_MODE replaces normal sender/receiver loops with dedicated test loops (src/test_mode.cpp). It sends/receives JSON test frames and publishes to smartmeter/<device_id>/test.