Expose timesync error in MQTT and web UI
BACKWARD-INCOMPATIBLE: MQTT faults payload now always includes err_last/err_last_text and err_last_age (schema change).
This commit is contained in:
@@ -221,6 +221,7 @@ Fixed header (little-endian):
|
|||||||
- `err_d` u8 (decode failures, sender-side counter)
|
- `err_d` u8 (decode failures, sender-side counter)
|
||||||
- `err_tx` u8 (LoRa TX 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)
|
- `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:
|
Body:
|
||||||
- `E0` u32 (absolute energy in Wh)
|
- `E0` u32 (absolute energy in Wh)
|
||||||
|
|||||||
@@ -10,6 +10,21 @@ static PubSubClient mqtt_client(wifi_client);
|
|||||||
static WifiMqttConfig g_cfg;
|
static WifiMqttConfig g_cfg;
|
||||||
static String g_client_id;
|
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) {
|
void mqtt_init(const WifiMqttConfig &config, const char *device_id) {
|
||||||
g_cfg = config;
|
g_cfg = config;
|
||||||
mqtt_client.setServer(config.mqtt_host.c_str(), config.mqtt_port);
|
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_m"] = counters.meter_read_fail;
|
||||||
doc["err_d"] = counters.decode_fail;
|
doc["err_d"] = counters.decode_fail;
|
||||||
doc["err_tx"] = counters.lora_tx_fail;
|
doc["err_tx"] = counters.lora_tx_fail;
|
||||||
if (last_error != FaultType::None) {
|
doc["err_last"] = static_cast<uint8_t>(last_error);
|
||||||
doc["err_last"] = static_cast<uint8_t>(last_error);
|
doc["err_last_text"] = fault_text(last_error);
|
||||||
doc["err_last_age"] = last_error_age_sec;
|
doc["err_last_age"] = last_error != FaultType::None ? last_error_age_sec : 0;
|
||||||
}
|
|
||||||
|
|
||||||
String payload;
|
String payload;
|
||||||
size_t len = serializeJson(doc, 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_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_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_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 }}");
|
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;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,21 @@ static bool auth_required() {
|
|||||||
return g_is_ap ? WEB_AUTH_REQUIRE_AP : WEB_AUTH_REQUIRE_STA;
|
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() {
|
static bool ensure_auth() {
|
||||||
if (!auth_required()) {
|
if (!auth_required()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -97,6 +112,7 @@ static String format_faults(uint8_t idx) {
|
|||||||
s += String(g_sender_faults[idx].lora_tx_fail);
|
s += String(g_sender_faults[idx].lora_tx_fail);
|
||||||
s += " last:";
|
s += " last:";
|
||||||
s += String(static_cast<uint8_t>(g_sender_last_errors[idx]));
|
s += String(static_cast<uint8_t>(g_sender_last_errors[idx]));
|
||||||
|
s += " (" + String(fault_text(g_sender_last_errors[idx])) + ")";
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +351,7 @@ static String render_sender_block(const SenderStatus &status) {
|
|||||||
if (status.has_data) {
|
if (status.has_data) {
|
||||||
s += " err_tx:" + String(status.last_data.err_lora_tx);
|
s += " err_tx:" + String(status.last_data.err_lora_tx);
|
||||||
s += " err_last:" + String(static_cast<uint8_t>(status.last_data.last_error));
|
s += " err_last:" + String(static_cast<uint8_t>(status.last_data.last_error));
|
||||||
|
s += " (" + String(fault_text(status.last_data.last_error)) + ")";
|
||||||
}
|
}
|
||||||
s += format_faults(idx);
|
s += format_faults(idx);
|
||||||
s += "<br>";
|
s += "<br>";
|
||||||
@@ -588,7 +605,7 @@ static void handle_sender() {
|
|||||||
html += "<td>" + String(d.link_rssi_dbm) + "</td>";
|
html += "<td>" + String(d.link_rssi_dbm) + "</td>";
|
||||||
html += "<td>" + String(d.link_snr_db, 1) + "</td>";
|
html += "<td>" + String(d.link_snr_db, 1) + "</td>";
|
||||||
html += "<td>" + String(d.err_lora_tx) + "</td>";
|
html += "<td>" + String(d.err_lora_tx) + "</td>";
|
||||||
html += "<td>" + String(static_cast<uint8_t>(d.last_error)) + "</td>";
|
html += "<td>" + String(static_cast<uint8_t>(d.last_error)) + " (" + String(fault_text(d.last_error)) + ")</td>";
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
}
|
}
|
||||||
html += "</table>";
|
html += "</table>";
|
||||||
|
|||||||
Reference in New Issue
Block a user