diff --git a/README.md b/README.md index cda4709..e6f59ee 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ MeterBatch JSON (compressed over LoRa) uses per-field arrays with integer units "t_last": 1738288030, "dt_s": 1, "n": 3, - "energy_wh": [123456700, 123456701, 123456701], + "e_wh": [123456700, 123456701, 123456701], "p_w": [930, 940, 950], "p1_w": [480, 490, 500], "p2_w": [450, 450, 450], diff --git a/src/json_codec.cpp b/src/json_codec.cpp index 24a5017..e3e88c6 100644 --- a/src/json_codec.cpp +++ b/src/json_codec.cpp @@ -205,7 +205,7 @@ bool meterBatchToJson(const MeterData *samples, size_t count, uint16_t batch_id, doc["bat_v"] = serialized(bat_buf); } - JsonArray energy = doc.createNestedArray("energy_wh"); + JsonArray energy = doc.createNestedArray("e_wh"); JsonArray p_w = doc.createNestedArray("p_w"); JsonArray p1_w = doc.createNestedArray("p1_w"); JsonArray p2_w = doc.createNestedArray("p2_w"); @@ -258,7 +258,7 @@ bool jsonToMeterBatch(const String &json, MeterData *out_samples, size_t max_cou uint32_t t_first = doc["t_first"] | t0; uint32_t t_last = doc["t_last"] | t_first; uint32_t dt_s = doc["dt_s"] | 1; - JsonArray energy = doc["energy_wh"].as(); + JsonArray energy = doc["e_wh"].as(); JsonArray p_w = doc["p_w"].as(); JsonArray p1_w = doc["p1_w"].as(); JsonArray p2_w = doc["p2_w"].as(); diff --git a/src/main.cpp b/src/main.cpp index eb12144..c795fe3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,6 +81,7 @@ static uint16_t g_last_sent_batch_id = 0; static uint16_t g_last_acked_batch_id = 0; static uint8_t g_batch_retry_count = 0; static bool g_batch_ack_pending = false; +static uint32_t g_batch_ack_timeout_ms = BATCH_ACK_TIMEOUT_MS; static MeterData g_inflight_samples[METER_BATCH_MAX_SAMPLES]; static uint8_t g_inflight_count = 0; static uint16_t g_inflight_batch_id = 0; @@ -294,6 +295,17 @@ static uint32_t compute_batch_rx_timeout_ms(uint16_t total_len, uint8_t chunk_co return timeout_ms < 10000 ? 10000 : timeout_ms; } +static uint32_t compute_batch_ack_timeout_ms(size_t payload_len) { + if (payload_len == 0) { + return 10000; + } + uint8_t chunk_count = static_cast((payload_len + BATCH_CHUNK_PAYLOAD - 1) / BATCH_CHUNK_PAYLOAD); + size_t packet_len = 5 + BATCH_HEADER_SIZE + (payload_len > BATCH_CHUNK_PAYLOAD ? BATCH_CHUNK_PAYLOAD : payload_len) + 2; + uint32_t per_chunk_toa_ms = lora_airtime_ms(packet_len); + uint32_t timeout_ms = static_cast(chunk_count) * per_chunk_toa_ms + BATCH_RX_MARGIN_MS; + return timeout_ms < 10000 ? 10000 : timeout_ms; +} + static bool inject_batch_meta(String &json, int16_t rssi_dbm, float snr_db, uint32_t rx_ts_utc) { DynamicJsonDocument doc(8192); DeserializationError err = deserializeJson(doc, json); @@ -423,6 +435,7 @@ static bool send_inflight_batch(uint32_t ts_for_display) { if (SERIAL_DEBUG_MODE && compress_ms > 200) { serial_debug_printf("tx: compress took %lums", static_cast(compress_ms)); } + g_batch_ack_timeout_ms = compute_batch_ack_timeout_ms(compressed_len); uint32_t send_start = millis(); bool ok = send_batch_payload(compressed, compressed_len, ts_for_display, g_inflight_batch_id); @@ -668,6 +681,29 @@ static void sender_loop() { send_meter_batch(last_sample_ts()); } + if (g_batch_ack_pending) { + uint32_t end_ms = millis() + 400; + while (millis() < end_ms) { + LoraPacket ack_pkt = {}; + if (!lora_receive(ack_pkt, 0) || ack_pkt.protocol_version != PROTOCOL_VERSION) { + delay(5); + continue; + } + if (ack_pkt.payload_type == PayloadType::Ack && ack_pkt.payload_len >= 6 && ack_pkt.role == DeviceRole::Receiver) { + uint16_t ack_id = read_u16_le(ack_pkt.payload); + uint16_t ack_sender = read_u16_le(&ack_pkt.payload[2]); + uint16_t ack_receiver = read_u16_le(&ack_pkt.payload[4]); + if (ack_sender == g_short_id && ack_receiver == ack_pkt.device_id_short && + g_batch_ack_pending && ack_id == g_last_sent_batch_id) { + g_last_acked_batch_id = ack_id; + serial_debug_printf("ack: ok batch_id=%u", ack_id); + finish_inflight_batch(); + break; + } + } + } + } + LoraPacket rx = {}; if (lora_receive(rx, 0) && rx.protocol_version == PROTOCOL_VERSION) { if (rx.payload_type == PayloadType::TimeSync) { @@ -685,7 +721,7 @@ static void sender_loop() { } } - if (g_batch_ack_pending && (now_ms - g_last_batch_send_ms >= BATCH_ACK_TIMEOUT_MS)) { + if (g_batch_ack_pending && (now_ms - g_last_batch_send_ms >= g_batch_ack_timeout_ms)) { if (g_batch_retry_count < BATCH_MAX_RETRIES) { g_batch_retry_count++; serial_debug_printf("ack: timeout batch_id=%u retry=%u", g_inflight_batch_id, g_batch_retry_count);