refactor lora payload timing

Bump the batch payload codec to schema v4 with separate meter-time and UTC anchors, then use meter seconds for sparse batch slotting and receiver reconstruction.

Update the current 868 MHz bench configuration, allow ACKs from configured receiver short IDs, improve AP-to-STA recovery, quiet the test build, and document the changed protocol in the README.
This commit is contained in:
2026-06-30 12:19:27 +02:00
parent 664ff1d744
commit def09160d0
13 changed files with 242 additions and 133 deletions
+2 -1
View File
@@ -5,7 +5,8 @@
struct BatchInput {
uint16_t sender_id;
uint16_t batch_id;
uint32_t t_last;
uint32_t meter_t_last;
uint32_t ts_utc_last;
uint32_t present_mask;
uint8_t n;
uint16_t battery_mV;
+15 -8
View File
@@ -2,8 +2,9 @@
#include <limits.h>
static constexpr uint16_t kMagic = 0xDDB3;
// Breaking change: schema v3 replaces fixed dt_s spacing with a 30-bit present_mask.
static constexpr uint8_t kSchema = 3;
// Breaking change: schema v4 uses a 30-bit present_mask in meter_seconds space
// plus a ts_utc anchor for receiver-side wall-clock reconstruction.
static constexpr uint8_t kSchema = 4;
static constexpr uint8_t kFlags = 0x01;
static constexpr size_t kMaxSamples = 30;
static constexpr uint32_t kPresentMaskValidBits = 0x3FFFFFFFUL;
@@ -125,7 +126,7 @@ bool encode_batch(const BatchInput &in, uint8_t *out, size_t out_cap, size_t *ou
return false;
}
size_t pos = 0;
if (!ensure_capacity(24, out_cap, pos)) {
if (!ensure_capacity(28, out_cap, pos)) {
return false;
}
write_u16_le(&out[pos], kMagic);
@@ -136,7 +137,9 @@ bool encode_batch(const BatchInput &in, uint8_t *out, size_t out_cap, size_t *ou
pos += 2;
write_u16_le(&out[pos], in.batch_id);
pos += 2;
write_u32_le(&out[pos], in.t_last);
write_u32_le(&out[pos], in.meter_t_last);
pos += 4;
write_u32_le(&out[pos], in.ts_utc_last);
pos += 4;
write_u32_le(&out[pos], in.present_mask);
pos += 4;
@@ -207,7 +210,7 @@ bool decode_batch(const uint8_t *buf, size_t len, BatchInput *out) {
return false;
}
size_t pos = 0;
if (len < 24) {
if (len < 28) {
return false;
}
uint16_t magic = read_u16_le(&buf[pos]);
@@ -221,7 +224,9 @@ bool decode_batch(const uint8_t *buf, size_t len, BatchInput *out) {
pos += 2;
out->batch_id = read_u16_le(&buf[pos]);
pos += 2;
out->t_last = read_u32_le(&buf[pos]);
out->meter_t_last = read_u32_le(&buf[pos]);
pos += 4;
out->ts_utc_last = read_u32_le(&buf[pos]);
pos += 4;
out->present_mask = read_u32_le(&buf[pos]);
pos += 4;
@@ -319,7 +324,8 @@ bool payload_codec_self_test() {
BatchInput in = {};
in.sender_id = 1;
in.batch_id = 42;
in.t_last = 1700000000;
in.meter_t_last = 123456789;
in.ts_utc_last = 1700000000;
in.present_mask = (1UL << 0) | (1UL << 2) | (1UL << 3) | (1UL << 10) | (1UL << 29);
in.n = 5;
in.battery_mV = 3750;
@@ -362,7 +368,8 @@ bool payload_codec_self_test() {
return false;
}
if (out.sender_id != in.sender_id || out.batch_id != in.batch_id || out.t_last != in.t_last ||
if (out.sender_id != in.sender_id || out.batch_id != in.batch_id || out.meter_t_last != in.meter_t_last ||
out.ts_utc_last != in.ts_utc_last ||
out.present_mask != in.present_mask || out.n != in.n || out.battery_mV != in.battery_mV ||
out.err_m != in.err_m || out.err_d != in.err_d || out.err_tx != in.err_tx || out.err_last != in.err_last ||
out.err_rx_reject != in.err_rx_reject) {