document batching updates and restore bat_v in batches
This commit is contained in:
20
README.md
20
README.md
@@ -59,7 +59,7 @@ void sender_loop() {
|
|||||||
read_battery(data); // VBAT + SoC
|
read_battery(data); // VBAT + SoC
|
||||||
|
|
||||||
if (time_to_send_batch()) {
|
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);
|
compressed = compressBuffer(json);
|
||||||
lora_send(packet(MeterBatch, compressed));
|
lora_send(packet(MeterBatch, compressed));
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ bool lora_send(const LoraPacket &pkt); // add header + CRC16 and transmit
|
|||||||
- Web UI:
|
- Web UI:
|
||||||
- AP mode: status + WiFi/MQTT config.
|
- AP mode: status + WiFi/MQTT config.
|
||||||
- STA mode: status + per-sender pages.
|
- 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)**:
|
**Receiver loop (pseudo-code)**:
|
||||||
```cpp
|
```cpp
|
||||||
@@ -106,7 +106,7 @@ void receiver_loop() {
|
|||||||
}
|
}
|
||||||
} else if (pkt.type == MeterBatch) {
|
} else if (pkt.type == MeterBatch) {
|
||||||
json = reassemble_and_decompress_batch(pkt);
|
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);
|
update_sender_status(sample);
|
||||||
mqtt_publish_state(sample);
|
mqtt_publish_state(sample);
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ Packet layout:
|
|||||||
|
|
||||||
LoRa radio settings:
|
LoRa radio settings:
|
||||||
- Frequency: **433 MHz** or **868 MHz** (set by build env via `LORA_FREQUENCY_HZ`)
|
- 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
|
## Data Format
|
||||||
JSON payload (sender + MQTT):
|
JSON payload (sender + MQTT):
|
||||||
@@ -203,6 +203,8 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units
|
|||||||
"sender": "s01",
|
"sender": "s01",
|
||||||
"batch_id": 1842,
|
"batch_id": 1842,
|
||||||
"t0": 1738288000,
|
"t0": 1738288000,
|
||||||
|
"t_first": 1738288000,
|
||||||
|
"t_last": 1738288030,
|
||||||
"dt_s": 1,
|
"dt_s": 1,
|
||||||
"n": 3,
|
"n": 3,
|
||||||
"energy_wh": [123456700, 123456701, 123456701],
|
"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],
|
"p1_w": [480, 490, 500],
|
||||||
"p2_w": [450, 450, 450],
|
"p2_w": [450, 450, 450],
|
||||||
"p3_w": [0, 0, 0],
|
"p3_w": [0, 0, 0],
|
||||||
|
"bat_v": 3.92,
|
||||||
"meta": {
|
"meta": {
|
||||||
"rssi": -92,
|
"rssi": -92,
|
||||||
"snr": 7.5,
|
"snr": 7.5,
|
||||||
@@ -221,6 +224,7 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units
|
|||||||
Notes:
|
Notes:
|
||||||
- `sender` maps to `EXPECTED_SENDER_IDS` order (`s01` = first sender).
|
- `sender` maps to `EXPECTED_SENDER_IDS` order (`s01` = first sender).
|
||||||
- `meta` is injected by the receiver after batch reassembly.
|
- `meta` is injected by the receiver after batch reassembly.
|
||||||
|
- `bat_v` is a single batch-level value (percent is calculated locally).
|
||||||
|
|
||||||
## Device IDs
|
## Device IDs
|
||||||
- Derived from WiFi STA MAC.
|
- Derived from WiFi STA MAC.
|
||||||
@@ -236,9 +240,7 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
|||||||
|
|
||||||
## OLED Behavior
|
## OLED Behavior
|
||||||
- Sender: OLED stays **ON for 10 seconds** on each wake, then powers down for sleep.
|
- Sender: OLED stays **ON for 10 seconds** on each wake, then powers down for sleep.
|
||||||
- Receiver: OLED follows the 10-minute auto-off behavior:
|
- Receiver: OLED is always on (no auto-off).
|
||||||
- GPIO14 HIGH: OLED forced ON.
|
|
||||||
- GPIO14 LOW: auto-off after 10 minutes.
|
|
||||||
- Pages rotate every 4s.
|
- Pages rotate every 4s.
|
||||||
|
|
||||||
## Power & Battery
|
## Power & Battery
|
||||||
@@ -283,6 +285,10 @@ Key timing settings in `include/config.h`:
|
|||||||
- `METER_SEND_INTERVAL_MS`
|
- `METER_SEND_INTERVAL_MS`
|
||||||
- `BATCH_ACK_TIMEOUT_MS`
|
- `BATCH_ACK_TIMEOUT_MS`
|
||||||
- `BATCH_MAX_RETRIES`
|
- `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
|
## Limits & Known Constraints
|
||||||
- **Compression**: uses lightweight RLE (good for JSON but not optimal).
|
- **Compression**: uses lightweight RLE (good for JSON but not optimal).
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ constexpr uint8_t BATCH_QUEUE_DEPTH = 10;
|
|||||||
constexpr BatchRetryPolicy BATCH_RETRY_POLICY = BatchRetryPolicy::Keep;
|
constexpr BatchRetryPolicy BATCH_RETRY_POLICY = BatchRetryPolicy::Keep;
|
||||||
constexpr uint32_t WATCHDOG_TIMEOUT_SEC = 120;
|
constexpr uint32_t WATCHDOG_TIMEOUT_SEC = 120;
|
||||||
constexpr bool ENABLE_HA_DISCOVERY = true;
|
constexpr bool ENABLE_HA_DISCOVERY = true;
|
||||||
constexpr bool SERIAL_DEBUG_MODE = true;
|
constexpr bool SERIAL_DEBUG_MODE = false;
|
||||||
constexpr bool SERIAL_DEBUG_DUMP_JSON = true;
|
constexpr bool SERIAL_DEBUG_DUMP_JSON = false;
|
||||||
constexpr bool LORA_SEND_BYPASS = false;
|
constexpr bool LORA_SEND_BYPASS = false;
|
||||||
|
|
||||||
constexpr uint8_t NUM_SENDERS = 1;
|
constexpr uint8_t NUM_SENDERS = 1;
|
||||||
|
|||||||
@@ -199,6 +199,11 @@ bool meterBatchToJson(const MeterData *samples, size_t count, uint16_t batch_id,
|
|||||||
if (last_error != FaultType::None) {
|
if (last_error != FaultType::None) {
|
||||||
doc["err_last"] = static_cast<uint8_t>(last_error);
|
doc["err_last"] = static_cast<uint8_t>(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 energy = doc.createNestedArray("energy_wh");
|
||||||
JsonArray p_w = doc.createNestedArray("p_w");
|
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_m = doc["err_m"] | 0;
|
||||||
uint32_t err_tx = doc["err_tx"] | 0;
|
uint32_t err_tx = doc["err_tx"] | 0;
|
||||||
FaultType last_error = static_cast<FaultType>(doc["err_last"] | 0);
|
FaultType last_error = static_cast<FaultType>(doc["err_last"] | 0);
|
||||||
|
float bat_v = doc["bat_v"] | NAN;
|
||||||
|
|
||||||
if (!doc["schema"].isNull()) {
|
if (!doc["schema"].isNull()) {
|
||||||
if ((doc["schema"] | 0) != 1) {
|
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<float>(p1_w[idx] | 0);
|
data.phase_power_w[0] = static_cast<float>(p1_w[idx] | 0);
|
||||||
data.phase_power_w[1] = static_cast<float>(p2_w[idx] | 0);
|
data.phase_power_w[1] = static_cast<float>(p2_w[idx] | 0);
|
||||||
data.phase_power_w[2] = static_cast<float>(p3_w[idx] | 0);
|
data.phase_power_w[2] = static_cast<float>(p3_w[idx] | 0);
|
||||||
data.battery_voltage_v = NAN;
|
data.battery_voltage_v = bat_v;
|
||||||
data.battery_percent = 0;
|
if (!isnan(bat_v)) {
|
||||||
|
data.battery_percent = battery_percent_from_voltage(bat_v);
|
||||||
|
} else {
|
||||||
|
data.battery_percent = 0;
|
||||||
|
}
|
||||||
data.valid = true;
|
data.valid = true;
|
||||||
data.link_valid = false;
|
data.link_valid = false;
|
||||||
data.link_rssi_dbm = 0;
|
data.link_rssi_dbm = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user