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:
@@ -161,7 +161,7 @@ struct MeterSampleEvent {
|
||||
static QueueHandle_t g_meter_sample_queue = nullptr;
|
||||
static TaskHandle_t g_meter_reader_task = nullptr;
|
||||
static bool g_meter_reader_task_running = false;
|
||||
static constexpr UBaseType_t METER_SAMPLE_QUEUE_LEN = 8;
|
||||
static constexpr UBaseType_t METER_SAMPLE_QUEUE_LEN = 32;
|
||||
static constexpr uint32_t METER_READER_TASK_STACK_WORDS = 4096;
|
||||
static constexpr UBaseType_t METER_READER_TASK_PRIORITY = 2;
|
||||
static constexpr BaseType_t METER_READER_TASK_CORE = 0;
|
||||
@@ -778,6 +778,15 @@ static uint16_t short_id_from_sender_id(uint16_t sender_id) {
|
||||
return EXPECTED_SENDER_IDS[sender_id - 1];
|
||||
}
|
||||
|
||||
static bool is_expected_receiver_short_id(uint16_t short_id) {
|
||||
for (uint8_t i = 0; i < NUM_RECEIVERS; ++i) {
|
||||
if (EXPECTED_RECEIVER_IDS[i] == short_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t kwh_to_wh_from_float(float value) {
|
||||
if (isnan(value)) {
|
||||
return 0;
|
||||
@@ -1001,7 +1010,9 @@ static bool send_inflight_batch(uint32_t ts_for_display) {
|
||||
BatchInput input = {};
|
||||
input.sender_id = sender_id_from_short_id(g_short_id);
|
||||
input.batch_id = g_inflight_batch_id;
|
||||
input.t_last = g_inflight_sync_request ? time_get_utc() :
|
||||
input.meter_t_last = g_inflight_sync_request ? 0 :
|
||||
g_inflight_samples[g_inflight_count - 1].meter_seconds;
|
||||
input.ts_utc_last = g_inflight_sync_request ? time_get_utc() :
|
||||
g_inflight_samples[g_inflight_count - 1].ts_utc;
|
||||
input.present_mask = 0;
|
||||
input.n = 0;
|
||||
@@ -1018,22 +1029,23 @@ static bool send_inflight_batch(uint32_t ts_for_display) {
|
||||
uint8_t ts_collapsed = 0;
|
||||
|
||||
if (!g_inflight_sync_request) {
|
||||
if (input.t_last < static_cast<uint32_t>(METER_BATCH_MAX_SAMPLES - 1)) {
|
||||
if (!g_inflight_samples[g_inflight_count - 1].meter_seconds_valid ||
|
||||
input.meter_t_last < static_cast<uint32_t>(METER_BATCH_MAX_SAMPLES - 1)) {
|
||||
g_last_tx_build_error = TxBuildError::Encode;
|
||||
return false;
|
||||
}
|
||||
const uint32_t window_start = input.t_last - static_cast<uint32_t>(METER_BATCH_MAX_SAMPLES - 1);
|
||||
const uint32_t window_start = input.meter_t_last - static_cast<uint32_t>(METER_BATCH_MAX_SAMPLES - 1);
|
||||
MeterData slot_samples[METER_BATCH_MAX_SAMPLES];
|
||||
bool slot_used[METER_BATCH_MAX_SAMPLES] = {};
|
||||
for (uint8_t i = 0; i < g_inflight_count; ++i) {
|
||||
const MeterData &sample = g_inflight_samples[i];
|
||||
if (sample.ts_utc < window_start || sample.ts_utc > input.t_last) {
|
||||
if (!sample.meter_seconds_valid || sample.meter_seconds < window_start || sample.meter_seconds > input.meter_t_last) {
|
||||
if (ts_dropped < 255) {
|
||||
ts_dropped++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint8_t slot = static_cast<uint8_t>(sample.ts_utc - window_start);
|
||||
uint8_t slot = static_cast<uint8_t>(sample.meter_seconds - window_start);
|
||||
if (slot_used[slot] && ts_collapsed < 255) {
|
||||
ts_collapsed++;
|
||||
}
|
||||
@@ -1308,7 +1320,11 @@ static void sender_loop() {
|
||||
data.ts_utc = sample_ts_utc;
|
||||
data.valid = has_snapshot;
|
||||
|
||||
bool appended = append_meter_sample(data, meter_ok, has_snapshot);
|
||||
if (!data.meter_seconds_valid) {
|
||||
data.valid = false;
|
||||
}
|
||||
|
||||
bool appended = append_meter_sample(data, meter_ok, has_snapshot && data.meter_seconds_valid);
|
||||
(void)appended;
|
||||
display_set_last_meter(data);
|
||||
display_set_last_read(meter_ok, data.ts_utc);
|
||||
@@ -1426,8 +1442,10 @@ static void sender_loop() {
|
||||
ack_id);
|
||||
}
|
||||
} else if (sender_id_from_short_id(ack_pkt.device_id_short) == 0 &&
|
||||
ack_pkt.device_id_short != g_short_id) {
|
||||
// Reject ACKs from unknown device IDs to prevent spoofing.
|
||||
ack_pkt.device_id_short != g_short_id &&
|
||||
!is_expected_receiver_short_id(ack_pkt.device_id_short)) {
|
||||
// Reject ACKs from unknown device IDs to prevent spoofing, but allow
|
||||
// ACKs from configured receiver short-IDs.
|
||||
sender_note_rx_reject(RxRejectReason::DeviceIdMismatch, "ack");
|
||||
if (SERIAL_DEBUG_MODE) {
|
||||
serial_debug_printf("ack: reject device_id=%04X (unknown)",
|
||||
@@ -1656,7 +1674,7 @@ SenderStats SenderStateMachine::stats() const {
|
||||
}
|
||||
|
||||
void SenderStateMachine::handleMeterRead(uint32_t now_ms) {
|
||||
meter_reader_pump(now_ms);
|
||||
(void)now_ms;
|
||||
}
|
||||
|
||||
void SenderStateMachine::maybeSendBatch(uint32_t now_ms) {
|
||||
|
||||
Reference in New Issue
Block a user