diff --git a/include/data_model.h b/include/data_model.h index 5b825f8..eadf2d3 100644 --- a/include/data_model.h +++ b/include/data_model.h @@ -15,7 +15,8 @@ enum class RxRejectReason : uint8_t { InvalidMsgKind = 2, LengthMismatch = 3, DeviceIdMismatch = 4, - BatchIdMismatch = 5 + BatchIdMismatch = 5, + UnknownSender = 6 }; struct FaultCounters { diff --git a/src/data_model.cpp b/src/data_model.cpp index 0ecdbe9..34b2241 100644 --- a/src/data_model.cpp +++ b/src/data_model.cpp @@ -21,6 +21,8 @@ const char *rx_reject_reason_text(RxRejectReason reason) { return "device_id_mismatch"; case RxRejectReason::BatchIdMismatch: return "batch_id_mismatch"; + case RxRejectReason::UnknownSender: + return "unknown_sender"; default: return "none"; } diff --git a/src/main.cpp b/src/main.cpp index fbceb70..a4c9bc9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,8 @@ static uint32_t g_sender_sleep_ms = 0; static uint32_t g_sender_power_log_ms = 0; static RxRejectReason g_sender_rx_reject_reason = RxRejectReason::None; static uint32_t g_sender_rx_reject_log_ms = 0; +static RxRejectReason g_receiver_rx_reject_reason = RxRejectReason::None; +static uint32_t g_receiver_rx_reject_log_ms = 0; static MeterData g_last_meter_data = {}; static bool g_last_meter_valid = false; static uint32_t g_last_meter_rx_ms = 0; @@ -421,6 +423,18 @@ static void sender_note_rx_reject(RxRejectReason reason, const char *context) { } } +static void receiver_note_rx_reject(RxRejectReason reason, const char *context) { + if (reason == RxRejectReason::None) { + return; + } + g_receiver_rx_reject_reason = reason; + uint32_t now_ms = millis(); + if (SERIAL_DEBUG_MODE && now_ms - g_receiver_rx_reject_log_ms >= 1000) { + g_receiver_rx_reject_log_ms = now_ms; + serial_debug_printf("rx_reject: %s reason=%s", context, rx_reject_reason_text(reason)); + } +} + static BatchBuffer *batch_queue_peek() { if (g_batch_count == 0) { return nullptr; @@ -1419,30 +1433,51 @@ static void receiver_loop() { } } - bool duplicate = sender_idx >= 0 && g_last_batch_id_rx[sender_idx] == batch_id; - if (sender_idx >= 0) { - SenderStatus &status = g_sender_statuses[sender_idx]; - if (status.rx_batches_total < UINT32_MAX) { - status.rx_batches_total++; - } - if (duplicate) { - if (status.rx_batches_duplicate < UINT32_MAX) { - status.rx_batches_duplicate++; - } - uint32_t duplicate_ts = time_get_utc(); - if (duplicate_ts == 0) { - duplicate_ts = batch.t_last; - } - status.rx_last_duplicate_ts_utc = duplicate_ts; - } + if (sender_idx < 0) { + receiver_note_rx_reject(RxRejectReason::UnknownSender, "batch"); + note_fault(g_receiver_faults, g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms, FaultType::Decode); + display_set_last_error(g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms); + serial_debug_printf("batch: reject unknown_sender short_id=%04X sender_id=%u batch_id=%u", + pkt.device_id_short, + static_cast(batch.sender_id), + static_cast(batch_id)); + goto receiver_loop_done; } + + uint16_t expected_sender_id = static_cast(sender_idx + 1); + if (batch.sender_id != expected_sender_id) { + receiver_note_rx_reject(RxRejectReason::DeviceIdMismatch, "batch"); + note_fault(g_receiver_faults, g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms, FaultType::Decode); + display_set_last_error(g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms); + serial_debug_printf("batch: reject device_id_mismatch short_id=%04X sender_id=%u expected=%u batch_id=%u", + pkt.device_id_short, + static_cast(batch.sender_id), + static_cast(expected_sender_id), + static_cast(batch_id)); + goto receiver_loop_done; + } + + bool duplicate = g_last_batch_id_rx[sender_idx] == batch_id; + SenderStatus &status = g_sender_statuses[sender_idx]; + if (status.rx_batches_total < UINT32_MAX) { + status.rx_batches_total++; + } + if (duplicate) { + if (status.rx_batches_duplicate < UINT32_MAX) { + status.rx_batches_duplicate++; + } + uint32_t duplicate_ts = time_get_utc(); + if (duplicate_ts == 0) { + duplicate_ts = batch.t_last; + } + status.rx_last_duplicate_ts_utc = duplicate_ts; + } + send_batch_ack(batch_id, batch.n); if (duplicate) { goto receiver_loop_done; } - if (sender_idx >= 0) { - g_last_batch_id_rx[sender_idx] = batch_id; - } + g_last_batch_id_rx[sender_idx] = batch_id; if (batch.n == 0) { goto receiver_loop_done; } @@ -1520,26 +1555,24 @@ static void receiver_loop() { goto receiver_loop_done; } - if (sender_idx >= 0) { - web_server_set_last_batch(static_cast(sender_idx), samples, count); - for (size_t s = 0; s < count; ++s) { - mqtt_publish_state(samples[s]); - } - g_sender_statuses[sender_idx].last_data = samples[count - 1]; - g_sender_statuses[sender_idx].last_update_ts_utc = samples[count - 1].ts_utc; - g_sender_statuses[sender_idx].has_data = true; - g_sender_faults_remote[sender_idx].meter_read_fail = samples[count - 1].err_meter_read; - g_sender_faults_remote[sender_idx].lora_tx_fail = samples[count - 1].err_lora_tx; - g_sender_last_error_remote[sender_idx] = samples[count - 1].last_error; - g_sender_last_error_remote_utc[sender_idx] = time_get_utc(); - g_sender_last_error_remote_ms[sender_idx] = millis(); - if (ENABLE_HA_DISCOVERY && !g_sender_discovery_sent[sender_idx]) { - g_sender_discovery_sent[sender_idx] = mqtt_publish_discovery(samples[count - 1].device_id); - } - publish_faults_if_needed(samples[count - 1].device_id, g_sender_faults_remote[sender_idx], g_sender_faults_remote_published[sender_idx], - g_sender_last_error_remote[sender_idx], g_sender_last_error_remote_published[sender_idx], - g_sender_last_error_remote_utc[sender_idx], g_sender_last_error_remote_ms[sender_idx]); + web_server_set_last_batch(static_cast(sender_idx), samples, count); + for (size_t s = 0; s < count; ++s) { + mqtt_publish_state(samples[s]); } + g_sender_statuses[sender_idx].last_data = samples[count - 1]; + g_sender_statuses[sender_idx].last_update_ts_utc = samples[count - 1].ts_utc; + g_sender_statuses[sender_idx].has_data = true; + g_sender_faults_remote[sender_idx].meter_read_fail = samples[count - 1].err_meter_read; + g_sender_faults_remote[sender_idx].lora_tx_fail = samples[count - 1].err_lora_tx; + g_sender_last_error_remote[sender_idx] = samples[count - 1].last_error; + g_sender_last_error_remote_utc[sender_idx] = time_get_utc(); + g_sender_last_error_remote_ms[sender_idx] = millis(); + if (ENABLE_HA_DISCOVERY && !g_sender_discovery_sent[sender_idx]) { + g_sender_discovery_sent[sender_idx] = mqtt_publish_discovery(samples[count - 1].device_id); + } + publish_faults_if_needed(samples[count - 1].device_id, g_sender_faults_remote[sender_idx], g_sender_faults_remote_published[sender_idx], + g_sender_last_error_remote[sender_idx], g_sender_last_error_remote_published[sender_idx], + g_sender_last_error_remote_utc[sender_idx], g_sender_last_error_remote_ms[sender_idx]); } else if (decode_error) { note_fault(g_receiver_faults, g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms, FaultType::Decode); display_set_last_error(g_receiver_last_error, g_receiver_last_error_utc, g_receiver_last_error_ms); diff --git a/src/web_server.cpp b/src/web_server.cpp index c68dbb3..6fadec3 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -698,7 +698,7 @@ static void handle_manual() { html += "
  • RSSI/SNR: LoRa link quality from last packet.
  • "; html += "
  • err_tx: sender-side LoRa TX error counter.
  • "; html += "
  • err_last: last error code (0=None, 1=MeterRead, 2=Decode, 3=LoraTx).
  • "; - html += "
  • rx_reject: last RX reject reason (0=None, 1=crc_fail, 2=invalid_msg_kind, 3=length_mismatch, 4=device_id_mismatch, 5=batch_id_mismatch).
  • "; + html += "
  • rx_reject: last RX reject reason (0=None, 1=crc_fail, 2=invalid_msg_kind, 3=length_mismatch, 4=device_id_mismatch, 5=batch_id_mismatch, 6=unknown_sender).
  • "; html += "
  • faults m/d/tx: receiver-side counters (meter read fails, decode fails, LoRa TX fails).
  • "; html += "
  • faults last: last receiver-side error code (same mapping as err_last).
  • "; html += "";