diff --git a/README.md b/README.md index b5fed95..dfc0a42 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ Fixed header (little-endian): - `err_d` u8 (decode failures, sender-side counter) - `err_tx` u8 (LoRa TX failures, sender-side counter) - `err_last` u8 (last error code: 0=None, 1=MeterRead, 2=Decode, 3=LoraTx, 4=TimeSync) +- MQTT faults payload also includes `err_last_text` (string) and `err_last_age` (seconds). Body: - `E0` u32 (absolute energy in Wh) diff --git a/src/mqtt_client.cpp b/src/mqtt_client.cpp index d08c616..2c6008f 100644 --- a/src/mqtt_client.cpp +++ b/src/mqtt_client.cpp @@ -10,6 +10,21 @@ static PubSubClient mqtt_client(wifi_client); static WifiMqttConfig g_cfg; static String g_client_id; +static const char *fault_text(FaultType fault) { + switch (fault) { + case FaultType::MeterRead: + return "meter"; + case FaultType::Decode: + return "decode"; + case FaultType::LoraTx: + return "loratx"; + case FaultType::TimeSync: + return "timesync"; + default: + return "none"; + } +} + void mqtt_init(const WifiMqttConfig &config, const char *device_id) { g_cfg = config; mqtt_client.setServer(config.mqtt_host.c_str(), config.mqtt_port); @@ -66,10 +81,9 @@ bool mqtt_publish_faults(const char *device_id, const FaultCounters &counters, F doc["err_m"] = counters.meter_read_fail; doc["err_d"] = counters.decode_fail; doc["err_tx"] = counters.lora_tx_fail; - if (last_error != FaultType::None) { - doc["err_last"] = static_cast(last_error); - doc["err_last_age"] = last_error_age_sec; - } + doc["err_last"] = static_cast(last_error); + doc["err_last_text"] = fault_text(last_error); + doc["err_last_age"] = last_error != FaultType::None ? last_error_age_sec : 0; String payload; size_t len = serializeJson(doc, payload); @@ -138,6 +152,8 @@ bool mqtt_publish_discovery(const char *device_id) { ok = ok && publish_discovery_sensor(device_id, "err_m", "Meter Read Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_m }}"); ok = ok && publish_discovery_sensor(device_id, "err_d", "Decode Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_d }}"); ok = ok && publish_discovery_sensor(device_id, "err_tx", "LoRa TX Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_tx }}"); + ok = ok && publish_discovery_sensor(device_id, "err_last", "Last Error Code", "", "", faults_topic.c_str(), "{{ value_json.err_last }}"); + ok = ok && publish_discovery_sensor(device_id, "err_last_text", "Last Error", "", "", faults_topic.c_str(), "{{ value_json.err_last_text }}"); ok = ok && publish_discovery_sensor(device_id, "err_last_age", "Last Error Age", "s", "", faults_topic.c_str(), "{{ value_json.err_last_age }}"); return ok; } diff --git a/src/web_server.cpp b/src/web_server.cpp index efc3217..0d24b98 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -59,6 +59,21 @@ static bool auth_required() { return g_is_ap ? WEB_AUTH_REQUIRE_AP : WEB_AUTH_REQUIRE_STA; } +static const char *fault_text(FaultType fault) { + switch (fault) { + case FaultType::MeterRead: + return "meter"; + case FaultType::Decode: + return "decode"; + case FaultType::LoraTx: + return "loratx"; + case FaultType::TimeSync: + return "timesync"; + default: + return "none"; + } +} + static bool ensure_auth() { if (!auth_required()) { return true; @@ -97,6 +112,7 @@ static String format_faults(uint8_t idx) { s += String(g_sender_faults[idx].lora_tx_fail); s += " last:"; s += String(static_cast(g_sender_last_errors[idx])); + s += " (" + String(fault_text(g_sender_last_errors[idx])) + ")"; return s; } @@ -335,6 +351,7 @@ static String render_sender_block(const SenderStatus &status) { if (status.has_data) { s += " err_tx:" + String(status.last_data.err_lora_tx); s += " err_last:" + String(static_cast(status.last_data.last_error)); + s += " (" + String(fault_text(status.last_data.last_error)) + ")"; } s += format_faults(idx); s += "
"; @@ -588,7 +605,7 @@ static void handle_sender() { html += "" + String(d.link_rssi_dbm) + ""; html += "" + String(d.link_snr_db, 1) + ""; html += "" + String(d.err_lora_tx) + ""; - html += "" + String(static_cast(d.last_error)) + ""; + html += "" + String(static_cast(d.last_error)) + " (" + String(fault_text(d.last_error)) + ")"; html += ""; } html += "";