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:
@@ -10,7 +10,8 @@ static void fill_sparse_batch(BatchInput &in) {
|
||||
memset(&in, 0, sizeof(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;
|
||||
@@ -45,7 +46,8 @@ static void fill_full_batch(BatchInput &in) {
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.sender_id = 1;
|
||||
in.batch_id = 0xBEEF;
|
||||
in.t_last = 1769904999;
|
||||
in.meter_t_last = 1769904999 - 29;
|
||||
in.ts_utc_last = 1769904999;
|
||||
in.present_mask = 0x3FFFFFFFUL;
|
||||
in.n = kMaxSamples;
|
||||
in.battery_mV = 4095;
|
||||
@@ -65,7 +67,8 @@ static void fill_full_batch(BatchInput &in) {
|
||||
static void assert_batch_equals(const BatchInput &expected, const BatchInput &actual) {
|
||||
TEST_ASSERT_EQUAL_UINT16(expected.sender_id, actual.sender_id);
|
||||
TEST_ASSERT_EQUAL_UINT16(expected.batch_id, actual.batch_id);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected.t_last, actual.t_last);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected.meter_t_last, actual.meter_t_last);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected.ts_utc_last, actual.ts_utc_last);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected.present_mask, actual.present_mask);
|
||||
TEST_ASSERT_EQUAL_UINT8(expected.n, actual.n);
|
||||
TEST_ASSERT_EQUAL_UINT16(expected.battery_mV, actual.battery_mV);
|
||||
@@ -89,14 +92,14 @@ static void assert_batch_equals(const BatchInput &expected, const BatchInput &ac
|
||||
}
|
||||
}
|
||||
|
||||
static void test_encode_decode_roundtrip_schema_v3() {
|
||||
static void test_encode_decode_roundtrip_schema_v4() {
|
||||
BatchInput in = {};
|
||||
fill_sparse_batch(in);
|
||||
|
||||
uint8_t encoded[256] = {};
|
||||
size_t encoded_len = 0;
|
||||
TEST_ASSERT_TRUE(encode_batch(in, encoded, sizeof(encoded), &encoded_len));
|
||||
TEST_ASSERT_TRUE(encoded_len > 24);
|
||||
TEST_ASSERT_TRUE(encoded_len > 28);
|
||||
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_TRUE(decode_batch(encoded, encoded_len, &out));
|
||||
@@ -199,12 +202,22 @@ static void test_encode_rejects_invalid_n_and_regression_cases() {
|
||||
}
|
||||
|
||||
static const uint8_t VECTOR_SYNC_EMPTY[] = {
|
||||
0xB3, 0xDD, 0x03, 0x01, 0x01, 0x00, 0x34, 0x12, 0xE4, 0x97, 0x7E, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x0E,
|
||||
0xB3, 0xDD, 0x04, 0x01, 0x01, 0x00, 0x34, 0x12,
|
||||
0x15, 0xCD, 0x5B, 0x07,
|
||||
0xE4, 0x97, 0x7E, 0x69,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
0xA6, 0x0E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
static const uint8_t VECTOR_SPARSE_5[] = {
|
||||
0xB3, 0xDD, 0x03, 0x01, 0x01, 0x00, 0x2A, 0x00, 0x00, 0xF1, 0x53, 0x65, 0x0D, 0x04, 0x00, 0x20, 0x05, 0xA6, 0x0E,
|
||||
0x02, 0x01, 0x03, 0x02, 0x01, 0xA0, 0x86, 0x01, 0x00, 0x01, 0x31, 0x00, 0x96, 0x01, 0x88, 0xFF, 0x3C, 0xA0, 0x1F,
|
||||
0xB3, 0xDD, 0x04, 0x01, 0x01, 0x00, 0x2A, 0x00,
|
||||
0x15, 0xCD, 0x5B, 0x07,
|
||||
0x00, 0xF1, 0x53, 0x65,
|
||||
0x0D, 0x04, 0x00, 0x20,
|
||||
0x05, 0xA6, 0x0E,
|
||||
0x02, 0x01, 0x03, 0x02, 0x01,
|
||||
0xA0, 0x86, 0x01, 0x00, 0x01, 0x31, 0x00, 0x96, 0x01, 0x88, 0xFF, 0x3C, 0xA0, 0x1F,
|
||||
0x9F, 0x1F, 0x9C, 0x09, 0x32, 0x00, 0x9F, 0x1F, 0xB4, 0x1F, 0xA0, 0x1F, 0xAB, 0x20, 0x00, 0x00, 0x14, 0x9F, 0x1F,
|
||||
0xA0, 0x1F, 0x14};
|
||||
|
||||
@@ -224,7 +237,8 @@ static void test_payload_golden_vectors() {
|
||||
BatchInput expected_sync = {};
|
||||
expected_sync.sender_id = 1;
|
||||
expected_sync.batch_id = 0x1234;
|
||||
expected_sync.t_last = 1769904100;
|
||||
expected_sync.meter_t_last = 123456789;
|
||||
expected_sync.ts_utc_last = 1769904100;
|
||||
expected_sync.present_mask = 0;
|
||||
expected_sync.n = 0;
|
||||
expected_sync.battery_mV = 3750;
|
||||
@@ -267,7 +281,7 @@ static void test_payload_golden_vectors() {
|
||||
void setup() {
|
||||
dd3_legacy_core_force_link();
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_encode_decode_roundtrip_schema_v3);
|
||||
RUN_TEST(test_encode_decode_roundtrip_schema_v4);
|
||||
RUN_TEST(test_decode_rejects_bad_magic_schema_flags);
|
||||
RUN_TEST(test_decode_rejects_truncated_and_length_mismatch);
|
||||
RUN_TEST(test_encode_and_decode_reject_invalid_present_mask);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
static void test_decode_batch_null_args() {
|
||||
uint8_t dummy[32] = {};
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_FALSE(decode_batch(nullptr, 24, &out));
|
||||
TEST_ASSERT_FALSE(decode_batch(dummy, 24, nullptr));
|
||||
TEST_ASSERT_FALSE(decode_batch(nullptr, 28, &out));
|
||||
TEST_ASSERT_FALSE(decode_batch(dummy, 28, nullptr));
|
||||
TEST_ASSERT_FALSE(decode_batch(nullptr, 0, nullptr));
|
||||
}
|
||||
|
||||
@@ -29,90 +29,103 @@ static void test_decode_batch_zero_length() {
|
||||
}
|
||||
|
||||
static void test_decode_batch_minimal_valid_sync() {
|
||||
// Sync-only (n=0) payload: 24 bytes header, no samples.
|
||||
uint8_t buf[24] = {};
|
||||
// Sync-only (n=0) payload: 28 bytes header, no samples.
|
||||
uint8_t buf[28] = {};
|
||||
// magic 0xDDB3 LE
|
||||
buf[0] = 0xB3; buf[1] = 0xDD;
|
||||
buf[2] = 3; // schema
|
||||
buf[2] = 4; // schema
|
||||
buf[3] = 0x01; // flags
|
||||
// sender_id=1
|
||||
buf[4] = 0x01; buf[5] = 0x00;
|
||||
// batch_id=1
|
||||
buf[6] = 0x01; buf[7] = 0x00;
|
||||
// t_last=1769904000 LE
|
||||
uint32_t t = 1769904000UL;
|
||||
buf[8] = t & 0xFF; buf[9] = (t >> 8) & 0xFF;
|
||||
buf[10] = (t >> 16) & 0xFF; buf[11] = (t >> 24) & 0xFF;
|
||||
// meter_t_last=123456789 LE
|
||||
uint32_t meter_t = 123456789UL;
|
||||
buf[8] = meter_t & 0xFF; buf[9] = (meter_t >> 8) & 0xFF;
|
||||
buf[10] = (meter_t >> 16) & 0xFF; buf[11] = (meter_t >> 24) & 0xFF;
|
||||
// ts_utc_last=1769904000 LE
|
||||
uint32_t utc_t = 1769904000UL;
|
||||
buf[12] = utc_t & 0xFF; buf[13] = (utc_t >> 8) & 0xFF;
|
||||
buf[14] = (utc_t >> 16) & 0xFF; buf[15] = (utc_t >> 24) & 0xFF;
|
||||
// present_mask=0
|
||||
buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = 0;
|
||||
buf[16] = 0; buf[17] = 0; buf[18] = 0; buf[19] = 0;
|
||||
// n=0
|
||||
buf[16] = 0;
|
||||
buf[20] = 0;
|
||||
// battery_mV=3750 LE
|
||||
buf[17] = 0xA6; buf[18] = 0x0E;
|
||||
buf[21] = 0xA6; buf[22] = 0x0E;
|
||||
// err fields
|
||||
buf[19] = 0; buf[20] = 0; buf[21] = 0; buf[22] = 0; buf[23] = 0;
|
||||
buf[23] = 0; buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0;
|
||||
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_TRUE(decode_batch(buf, 24, &out));
|
||||
TEST_ASSERT_TRUE(decode_batch(buf, 28, &out));
|
||||
TEST_ASSERT_EQUAL_UINT8(0, out.n);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, out.present_mask);
|
||||
}
|
||||
|
||||
static void test_decode_batch_n_exceeds_30() {
|
||||
// Forge a header with n=31, which should be rejected.
|
||||
uint8_t buf[24] = {};
|
||||
uint8_t buf[28] = {};
|
||||
buf[0] = 0xB3; buf[1] = 0xDD;
|
||||
buf[2] = 3; buf[3] = 0x01;
|
||||
buf[2] = 4; buf[3] = 0x01;
|
||||
buf[4] = 0x01; buf[5] = 0x00;
|
||||
buf[6] = 0x01; buf[7] = 0x00;
|
||||
uint32_t t = 1769904000UL;
|
||||
buf[8] = t & 0xFF; buf[9] = (t >> 8) & 0xFF;
|
||||
buf[10] = (t >> 16) & 0xFF; buf[11] = (t >> 24) & 0xFF;
|
||||
buf[12] = 0xFF; buf[13] = 0xFF; buf[14] = 0xFF; buf[15] = 0x3F; // all 30 bits set
|
||||
buf[16] = 31; // n=31 → must reject
|
||||
buf[17] = 0xA6; buf[18] = 0x0E;
|
||||
buf[19] = 0; buf[20] = 0; buf[21] = 0; buf[22] = 0; buf[23] = 0;
|
||||
uint32_t meter_t = 123456789UL;
|
||||
buf[8] = meter_t & 0xFF; buf[9] = (meter_t >> 8) & 0xFF;
|
||||
buf[10] = (meter_t >> 16) & 0xFF; buf[11] = (meter_t >> 24) & 0xFF;
|
||||
uint32_t utc_t = 1769904000UL;
|
||||
buf[12] = utc_t & 0xFF; buf[13] = (utc_t >> 8) & 0xFF;
|
||||
buf[14] = (utc_t >> 16) & 0xFF; buf[15] = (utc_t >> 24) & 0xFF;
|
||||
buf[16] = 0xFF; buf[17] = 0xFF; buf[18] = 0xFF; buf[19] = 0x3F; // all 30 bits set
|
||||
buf[20] = 31; // n=31 → must reject
|
||||
buf[21] = 0xA6; buf[22] = 0x0E;
|
||||
buf[23] = 0; buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0;
|
||||
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 24, &out));
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 28, &out));
|
||||
}
|
||||
|
||||
static void test_decode_batch_present_mask_n_mismatch() {
|
||||
// present_mask has 3 bits but n=5 → must reject.
|
||||
uint8_t buf[24] = {};
|
||||
uint8_t buf[28] = {};
|
||||
buf[0] = 0xB3; buf[1] = 0xDD;
|
||||
buf[2] = 3; buf[3] = 0x01;
|
||||
buf[2] = 4; buf[3] = 0x01;
|
||||
buf[4] = 0x01; buf[5] = 0x00;
|
||||
buf[6] = 0x01; buf[7] = 0x00;
|
||||
uint32_t t = 1769904000UL;
|
||||
buf[8] = t & 0xFF; buf[9] = (t >> 8) & 0xFF;
|
||||
buf[10] = (t >> 16) & 0xFF; buf[11] = (t >> 24) & 0xFF;
|
||||
buf[12] = 0x07; buf[13] = 0; buf[14] = 0; buf[15] = 0; // 3 bits
|
||||
buf[16] = 5; // n=5 but only 3 mask bits
|
||||
buf[17] = 0xA6; buf[18] = 0x0E;
|
||||
buf[19] = 0; buf[20] = 0; buf[21] = 0; buf[22] = 0; buf[23] = 0;
|
||||
uint32_t meter_t = 123456789UL;
|
||||
buf[8] = meter_t & 0xFF; buf[9] = (meter_t >> 8) & 0xFF;
|
||||
buf[10] = (meter_t >> 16) & 0xFF; buf[11] = (meter_t >> 24) & 0xFF;
|
||||
uint32_t utc_t = 1769904000UL;
|
||||
buf[12] = utc_t & 0xFF; buf[13] = (utc_t >> 8) & 0xFF;
|
||||
buf[14] = (utc_t >> 16) & 0xFF; buf[15] = (utc_t >> 24) & 0xFF;
|
||||
buf[16] = 0x07; buf[17] = 0; buf[18] = 0; buf[19] = 0; // 3 bits
|
||||
buf[20] = 5; // n=5 but only 3 mask bits
|
||||
buf[21] = 0xA6; buf[22] = 0x0E;
|
||||
buf[23] = 0; buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0;
|
||||
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 24, &out));
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 28, &out));
|
||||
}
|
||||
|
||||
static void test_decode_batch_reserved_mask_bits() {
|
||||
// Bit 30 or 31 set → must reject (only bits 0-29 valid).
|
||||
uint8_t buf[24] = {};
|
||||
uint8_t buf[28] = {};
|
||||
buf[0] = 0xB3; buf[1] = 0xDD;
|
||||
buf[2] = 3; buf[3] = 0x01;
|
||||
buf[2] = 4; buf[3] = 0x01;
|
||||
buf[4] = 0x01; buf[5] = 0x00;
|
||||
buf[6] = 0x01; buf[7] = 0x00;
|
||||
uint32_t t = 1769904000UL;
|
||||
buf[8] = t & 0xFF; buf[9] = (t >> 8) & 0xFF;
|
||||
buf[10] = (t >> 16) & 0xFF; buf[11] = (t >> 24) & 0xFF;
|
||||
buf[12] = 0x01; buf[13] = 0; buf[14] = 0; buf[15] = 0x40; // bit 30
|
||||
buf[16] = 1;
|
||||
buf[17] = 0xA6; buf[18] = 0x0E;
|
||||
buf[19] = 0; buf[20] = 0; buf[21] = 0; buf[22] = 0; buf[23] = 0;
|
||||
uint32_t meter_t = 123456789UL;
|
||||
buf[8] = meter_t & 0xFF; buf[9] = (meter_t >> 8) & 0xFF;
|
||||
buf[10] = (meter_t >> 16) & 0xFF; buf[11] = (meter_t >> 24) & 0xFF;
|
||||
uint32_t utc_t = 1769904000UL;
|
||||
buf[12] = utc_t & 0xFF; buf[13] = (utc_t >> 8) & 0xFF;
|
||||
buf[14] = (utc_t >> 16) & 0xFF; buf[15] = (utc_t >> 24) & 0xFF;
|
||||
buf[16] = 0x01; buf[17] = 0; buf[18] = 0; buf[19] = 0x40; // bit 30
|
||||
buf[20] = 1;
|
||||
buf[21] = 0xA6; buf[22] = 0x0E;
|
||||
buf[23] = 0; buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0;
|
||||
|
||||
BatchInput out = {};
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 24, &out));
|
||||
TEST_ASSERT_FALSE(decode_batch(buf, 28, &out));
|
||||
}
|
||||
|
||||
// ---- uleb128_decode: negative tests ----
|
||||
@@ -308,7 +321,8 @@ static void test_decode_batch_byte_flip_fuzz() {
|
||||
BatchInput in = {};
|
||||
in.sender_id = 1;
|
||||
in.batch_id = 42;
|
||||
in.t_last = 1769904000UL;
|
||||
in.meter_t_last = 123456789UL;
|
||||
in.ts_utc_last = 1769904000UL;
|
||||
in.present_mask = 0x07; // bits 0-2
|
||||
in.n = 3;
|
||||
in.battery_mV = 3750;
|
||||
|
||||
Reference in New Issue
Block a user