optional RTC 3231 integration
This commit is contained in:
73
README.md
73
README.md
@@ -21,6 +21,10 @@ Variants:
|
||||
- SCL: GPIO22
|
||||
- RST: **not used** (SSD1306 init uses `-1` reset pin)
|
||||
- I2C address: 0x3C
|
||||
- I2C RTC (DS3231)
|
||||
- SDA: GPIO21
|
||||
- SCL: GPIO22
|
||||
- I2C address: 0x68
|
||||
- Battery ADC: GPIO35 (via on-board divider)
|
||||
- **Role select**: GPIO13 (INPUT_PULLDOWN)
|
||||
- LOW = Sender
|
||||
@@ -44,20 +48,22 @@ Variants:
|
||||
- Phase voltage: 32.7 / 52.7 / 72.7
|
||||
- Reads battery voltage and estimates SoC.
|
||||
- Builds JSON payload, compresses, wraps in LoRa packet, transmits.
|
||||
- Deep sleeps between cycles.
|
||||
- Light sleeps between meter reads; batches are sent every 30s.
|
||||
- Listens for LoRa time sync packets to set UTC clock.
|
||||
- Uses DS3231 RTC after boot if no time sync has arrived yet.
|
||||
- OLED shows status + meter data pages.
|
||||
|
||||
**Sender flow (pseudo-code)**:
|
||||
```cpp
|
||||
void sender_cycle() {
|
||||
meter_read(data); // SML/OBIS -> MeterData
|
||||
read_battery(data); // VBAT + SoC
|
||||
data.ts_utc = time_get_utc_or_uptime();
|
||||
void sender_loop() {
|
||||
meter_read_every_second(); // SML/OBIS -> MeterData samples
|
||||
read_battery(data); // VBAT + SoC
|
||||
|
||||
json = meterDataToJson(data);
|
||||
compressed = compressBuffer(json);
|
||||
lora_send(packet(MeterData, compressed));
|
||||
if (time_to_send_batch()) {
|
||||
json = meterBatchToJson(samples);
|
||||
compressed = compressBuffer(json);
|
||||
lora_send(packet(MeterBatch, compressed));
|
||||
}
|
||||
|
||||
display_set_last_meter(data);
|
||||
display_set_last_read(ok);
|
||||
@@ -65,8 +71,7 @@ void sender_cycle() {
|
||||
display_tick();
|
||||
|
||||
lora_receive_time_sync(); // optional
|
||||
keep_oled_on_for_read_window();
|
||||
deep_sleep(SENDER_WAKE_INTERVAL_SEC);
|
||||
light_sleep_until_next_event();
|
||||
}
|
||||
```
|
||||
|
||||
@@ -92,16 +97,24 @@ bool lora_send(const LoraPacket &pkt); // add header + CRC16 and transmit
|
||||
**Receiver loop (pseudo-code)**:
|
||||
```cpp
|
||||
void receiver_loop() {
|
||||
if (lora_receive(pkt) && pkt.type == MeterData) {
|
||||
json = decompressBuffer(pkt.payload);
|
||||
if (jsonToMeterData(json, data)) {
|
||||
update_sender_status(data);
|
||||
mqtt_publish_state(data);
|
||||
if (lora_receive(pkt)) {
|
||||
if (pkt.type == MeterData) {
|
||||
json = decompressBuffer(pkt.payload);
|
||||
if (jsonToMeterData(json, data)) {
|
||||
update_sender_status(data);
|
||||
mqtt_publish_state(data);
|
||||
}
|
||||
} else if (pkt.type == MeterBatch) {
|
||||
json = reassemble_and_decompress_batch(pkt);
|
||||
for (sample in jsonToMeterBatch(json)) {
|
||||
update_sender_status(sample);
|
||||
mqtt_publish_state(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (time_to_send_timesync()) {
|
||||
time_send_timesync(self_short_id);
|
||||
time_send_timesync(self_short_id); // 60s for first 10 min, then hourly
|
||||
}
|
||||
|
||||
mqtt_loop();
|
||||
@@ -115,6 +128,7 @@ void receiver_loop() {
|
||||
```cpp
|
||||
bool lora_receive(LoraPacket &pkt, uint32_t timeout_ms);
|
||||
bool jsonToMeterData(const String &json, MeterData &data);
|
||||
bool jsonToMeterBatch(const String &json, MeterData *samples, size_t max, size_t &count);
|
||||
bool mqtt_publish_state(const MeterData &data);
|
||||
void web_server_loop(); // AP or STA UI
|
||||
void time_send_timesync(uint16_t self_id);
|
||||
@@ -155,7 +169,7 @@ Packet layout:
|
||||
[0] protocol_version (1)
|
||||
[1] role (0=sender, 1=receiver)
|
||||
[2..3] device_id_short (uint16)
|
||||
[4] payload_type (0=meter, 1=test, 2=time_sync)
|
||||
[4] payload_type (0=meter, 1=test, 2=time_sync, 3=meter_batch)
|
||||
[5..N-3] compressed payload
|
||||
[N-2..N-1] CRC16 (bytes 0..N-3)
|
||||
```
|
||||
@@ -169,16 +183,16 @@ JSON payload (sender + MQTT):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "dd3-01",
|
||||
"id": "F19C",
|
||||
"ts": 1737200000,
|
||||
"energy_kwh": 1234.567,
|
||||
"p_total_w": 950.0,
|
||||
"p1_w": 500.0,
|
||||
"p2_w": 450.0,
|
||||
"p3_w": 0.0,
|
||||
"v1_v": 230.1,
|
||||
"v2_v": 229.8,
|
||||
"v3_v": 231.0,
|
||||
"e_kwh": 1234.57,
|
||||
"p_w": 950.00,
|
||||
"p1_w": 500.00,
|
||||
"p2_w": 450.00,
|
||||
"p3_w": 0.00,
|
||||
"v1_v": 230.10,
|
||||
"v2_v": 229.80,
|
||||
"v3_v": 231.00,
|
||||
"bat_v": 3.92,
|
||||
"bat_pct": 78
|
||||
}
|
||||
@@ -188,6 +202,7 @@ JSON payload (sender + MQTT):
|
||||
- Derived from WiFi STA MAC.
|
||||
- `short_id = (MAC[4] << 8) | MAC[5]`
|
||||
- `device_id = dd3-%04X`
|
||||
- JSON `id` uses only the last 4 hex digits (e.g., `F19C`) to save airtime.
|
||||
|
||||
Receiver expects known senders in `include/config.h` via:
|
||||
```cpp
|
||||
@@ -225,6 +240,10 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
||||
## NTP
|
||||
- NTP servers are configurable in the web UI (`/wifi`).
|
||||
- Defaults: `pool.ntp.org` and `time.nist.gov`.
|
||||
## RTC (DS3231)
|
||||
- Optional DS3231 on the I2C bus. Connect SDA to GPIO21 and SCL to GPIO22 (same bus as the OLED).
|
||||
- Receiver time sync packets set the RTC.
|
||||
- On boot, if no LoRa time sync has arrived yet, the sender uses the RTC time as the initial `ts_utc`.
|
||||
|
||||
## Build Environments
|
||||
- `lilygo-t3-v1-6-1`: production build
|
||||
@@ -235,7 +254,7 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
||||
## Limits & Known Constraints
|
||||
- **Compression**: uses lightweight RLE (good for JSON but not optimal).
|
||||
- **OBIS parsing**: supports IEC 62056-21 ASCII (Mode D) and SML; may need tuning for some meters.
|
||||
- **Payload size**: JSON < 256 bytes (enforced by ArduinoJson static doc).
|
||||
- **Payload size**: single JSON frames < 256 bytes (ArduinoJson static doc); batch frames are chunked and reassembled.
|
||||
- **Battery ADC**: uses simple linear calibration constant in `power_manager.cpp`.
|
||||
- **OLED**: no hardware reset line is used (matches working reference).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user