Enhance MQTT message structure by adding cumulative packet statistics (packets_sent and packets_received) to the status message. Update related functions in MQTTMessageBuilder and MQTTBridge to handle new parameters and ensure proper JSON serialization. Document the new fields and their significance in the implementation notes.

This commit is contained in:
agessaman committed 2026-06-16 15:59:36 -07:00
1 parent e80a5ded1c
commit 8bf590b1e3
4 files changed
+55 -7

No files matched your search

+7 -2
View File
@@ -515,6 +515,8 @@ Minimal raw packet data for map integration.
"stats": {
"battery_mv": 4100,
"uptime_secs": 3600,
"packets_sent": 42,
"packets_received": 128,
"errors": 0,
"queue_len": 0,
"noise_floor": -110,
@@ -529,6 +531,7 @@ Minimal raw packet data for map integration.
**Notes:**
- Timestamps are always emitted in UTC with an explicit `+00:00` offset.
- The `stats` object is only included when at least one stat value is available; individual fields are omitted when their value is unavailable.
- `packets_sent` / `packets_received` are cumulative totals since boot (flood + direct), sourced from the dispatcher counters.
### Packet Message
```json
@@ -547,15 +550,17 @@ Minimal raw packet data for map integration.
"raw": "F5930103807E5F1E...",
"SNR": "12.5",
"RSSI": "-65",
"score": "234",
"hash": "A1B2C3D4E5F67890",
"path": ["aa", "bb", "cc"]
}
```
**Notes:**
- All numeric fields (`len`, `packet_type`, `payload_len`, `SNR`, `RSSI`) are formatted as JSON strings.
- All numeric fields (`len`, `packet_type`, `payload_len`, `SNR`, `RSSI`, `score`) are formatted as JSON strings.
- `time` and `date` are always UTC (`HH:MM:SS` and `DD/MM/YYYY`); `timestamp` is UTC with an explicit `+00:00` offset.
- `SNR` and `RSSI` are only present for RX packets (received from radio). TX packets omit these fields since the packet originates from this node.
- `SNR`, `RSSI`, and `score` are only present for RX packets (received from radio). TX packets omit these fields since the packet originates from this node.
- `score` is the firmware's rebroadcast score for the received packet (the same value used to compute flood-rebroadcast delay), scaled ×1000 to match the integer printed in the serial RX log — e.g. a score of `0.234` is emitted as `"234"` (range `0`–`1000`). It is recomputed at publish time from the packet's SNR and length via the radio's `packetScore()`, so it matches what the firmware used on receive. Omitted when unavailable (e.g. the non-PSRAM reconstruction-less fallback path).
- `path` is only present for direct-route packets that carry path data. It is a JSON array of lowercase hex hop tokens, one element per hop — e.g. `["aa","bb","cc"]` for single-byte hashes, or `["aaaa","bbbb"]` for multi-byte hashes. This matches the `path` representation emitted by [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture).
### Raw Message
+22 -2
View File
@@ -1,6 +1,7 @@
#include "MQTTMessageBuilder.h"
#include <ArduinoJson.h>
#include <cstring>
#include <math.h>
#include <time.h>
#include <Timezone.h>
#include "MeshCore.h"
@@ -40,6 +41,8 @@ int MQTTMessageBuilder::buildStatusMessage(
int rx_air_secs,
int recv_errors,
int internal_heap,
int packets_sent,
int packets_received,
const char* repeat
) {
// doc is provided by the caller (heap-allocated DynamicJsonDocument in MQTTBridge),
@@ -62,7 +65,7 @@ int MQTTMessageBuilder::buildStatusMessage(
// Add stats object if any stats are provided
if (battery_mv >= 0 || uptime_secs >= 0 || errors >= 0 || queue_len >= 0 ||
noise_floor > -999 || tx_air_secs >= 0 || rx_air_secs >= 0 || recv_errors >= 0 ||
internal_heap >= 0) {
internal_heap >= 0 || packets_sent >= 0 || packets_received >= 0) {
JsonObject stats = root.createNestedObject("stats");
if (battery_mv >= 0) {
@@ -71,6 +74,12 @@ int MQTTMessageBuilder::buildStatusMessage(
if (uptime_secs >= 0) {
stats["uptime_secs"] = uptime_secs;
}
if (packets_sent >= 0) {
stats["packets_sent"] = packets_sent;
}
if (packets_received >= 0) {
stats["packets_received"] = packets_received;
}
if (errors >= 0) {
stats["errors"] = errors;
}
@@ -113,6 +122,7 @@ int MQTTMessageBuilder::buildPacketMessage(
const char* raw,
float snr,
int rssi,
float score,
const char* hash,
const uint8_t* path_bytes,
int path_hop_count,
@@ -131,7 +141,8 @@ int MQTTMessageBuilder::buildPacketMessage(
char payload_len_str[16];
char snr_str[16];
char rssi_str[16];
char score_str[16];
snprintf(len_str, sizeof(len_str), "%d", len);
snprintf(packet_type_str, sizeof(packet_type_str), "%d", packet_type);
snprintf(payload_len_str, sizeof(payload_len_str), "%d", payload_len);
@@ -155,6 +166,12 @@ int MQTTMessageBuilder::buildPacketMessage(
if (strcmp(direction, "rx") == 0) {
root["SNR"] = snr_str;
root["RSSI"] = rssi_str;
// Firmware's rebroadcast "score" for this RX packet, scaled x1000 to match the
// integer form printed in the serial RX log (see Dispatcher::checkRecv()).
if (!isnan(score)) {
snprintf(score_str, sizeof(score_str), "%d", (int)(score * 1000));
root["score"] = score_str;
}
}
// Routing path as an array of lowercase hex hop tokens, one element per hop
@@ -262,6 +279,7 @@ int MQTTMessageBuilder::buildPacketJSON(
raw_hex,
12.5f, // SNR - using reasonable default
-65, // RSSI - using reasonable default
NAN, // score - unknown on this reconstruction-less fallback path
hash_str,
has_path ? packet->path : nullptr,
has_path ? packet->getPathHashCount() : 0,
@@ -280,6 +298,7 @@ int MQTTMessageBuilder::buildPacketJSONFromRaw(
const char* origin_id,
float snr,
float rssi,
float score,
Timezone* timezone,
char* buffer,
size_t buffer_size
@@ -333,6 +352,7 @@ int MQTTMessageBuilder::buildPacketJSONFromRaw(
raw_hex,
snr, // Use actual SNR from radio
rssi, // Use actual RSSI from radio
score, // Firmware rebroadcast score (NaN for tx / when unavailable)
hash_str,
has_path ? packet->path : nullptr,
has_path ? packet->getPathHashCount() : 0,
+4
View File
@@ -78,6 +78,8 @@ public:
int rx_air_secs = -1,
int recv_errors = -1,
int internal_heap = -1,
int packets_sent = -1,
int packets_received = -1,
const char* repeat = nullptr
);
@@ -120,6 +122,7 @@ public:
const char* raw,
float snr,
int rssi,
float score,
const char* hash,
const uint8_t* path_bytes,
int path_hop_count,
@@ -180,6 +183,7 @@ public:
const char* origin_id,
float snr,
float rssi,
float score,
Timezone* timezone,
char* buffer,
size_t buffer_size
+22 -3
View File
@@ -5,6 +5,7 @@
#include <WiFiUdp.h>
#include <Timezone.h>
#include <time.h>
#include <math.h>
#ifdef WITH_SNMP
#include "../SNMPAgent.h"
@@ -1730,6 +1731,8 @@ void MQTTBridge::publishStatusToSlot(int index) {
int tx_air_secs = -1;
int rx_air_secs = -1;
int recv_errors = -1;
int packets_sent = -1;
int packets_received = -1;
if (_board) battery_mv = _board->getBattMilliVolts();
if (_ms) uptime_secs = _ms->getMillis() / 1000;
@@ -1737,6 +1740,8 @@ void MQTTBridge::publishStatusToSlot(int index) {
errors = _dispatcher->getErrFlags();
tx_air_secs = _dispatcher->getTotalAirTime() / 1000;
rx_air_secs = _dispatcher->getReceiveAirTime() / 1000;
packets_sent = (int)(_dispatcher->getNumSentFlood() + _dispatcher->getNumSentDirect());
packets_received = (int)(_dispatcher->getNumRecvFlood() + _dispatcher->getNumRecvDirect());
}
if (_radio) {
noise_floor = (int16_t)_radio->getNoiseFloor();
@@ -1752,6 +1757,7 @@ void MQTTBridge::publishStatusToSlot(int index) {
client_version, "online", timestamp, json_buffer, STATUS_JSON_BUFFER_SIZE,
battery_mv, uptime_secs, errors, _queue_count, noise_floor,
tx_air_secs, rx_air_secs, recv_errors, internal_heap_free,
packets_sent, packets_received,
_prefs->disable_fwd ? "off" : "on"
);
@@ -2436,6 +2442,8 @@ bool MQTTBridge::publishStatus() {
int tx_air_secs = -1;
int rx_air_secs = -1;
int recv_errors = -1;
int packets_sent = -1;
int packets_received = -1;
if (_board) battery_mv = _board->getBattMilliVolts();
if (_ms) uptime_secs = _ms->getMillis() / 1000;
@@ -2443,6 +2451,8 @@ bool MQTTBridge::publishStatus() {
errors = _dispatcher->getErrFlags();
tx_air_secs = _dispatcher->getTotalAirTime() / 1000;
rx_air_secs = _dispatcher->getReceiveAirTime() / 1000;
packets_sent = (int)(_dispatcher->getNumSentFlood() + _dispatcher->getNumSentDirect());
packets_received = (int)(_dispatcher->getNumRecvFlood() + _dispatcher->getNumRecvDirect());
}
if (_radio) {
noise_floor = (int16_t)_radio->getNoiseFloor();
@@ -2458,6 +2468,7 @@ bool MQTTBridge::publishStatus() {
client_version, "online", timestamp, json_buffer, STATUS_JSON_BUFFER_SIZE,
battery_mv, uptime_secs, errors, _queue_count, noise_floor,
tx_air_secs, rx_air_secs, recv_errors, internal_heap_free,
packets_sent, packets_received,
_prefs->disable_fwd ? "off" : "on"
);
@@ -2545,19 +2556,26 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx,
strncpy(origin_id, _device_id, sizeof(origin_id) - 1);
origin_id[sizeof(origin_id) - 1] = '\0';
// Firmware rebroadcast "score" for this packet — only meaningful for RX packets
// (depends on receive SNR). Recomputed here exactly as Dispatcher::checkRecv() does,
// via the radio's packetScore(snr, len); NaN signals "omit" (tx, or no radio).
// buildPacketMessage scales it x1000 to match the integer in the serial RX log.
// Build packet message using raw radio data if provided
int len;
if (raw_data && raw_len > 0) {
float score = (_radio && !is_tx) ? _radio->packetScore(snr, raw_len) : NAN;
len = MQTTMessageBuilder::buildPacketJSONFromRaw(
_packet_json_doc,
raw_data, raw_len, packet, is_tx, _origin, origin_id,
snr, rssi, _timezone, active_buffer, active_buffer_size
snr, rssi, score, _timezone, active_buffer, active_buffer_size
);
} else if (!is_tx && _last_raw_data && _last_raw_len > 0 && (millis() - _last_raw_timestamp) < 1000) {
float score = _radio ? _radio->packetScore(_last_snr, _last_raw_len) : NAN;
len = MQTTMessageBuilder::buildPacketJSONFromRaw(
_packet_json_doc,
_last_raw_data, _last_raw_len, packet, is_tx, _origin, origin_id,
_last_snr, _last_rssi, _timezone, active_buffer, active_buffer_size
_last_snr, _last_rssi, score, _timezone, active_buffer, active_buffer_size
);
} else {
// Reconstruct wire-format bytes from packet (same as MQTTMessageBuilder::packetToHex).
@@ -2566,10 +2584,11 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx,
uint8_t reconstructed[512];
uint8_t rlen = packet->writeTo(reconstructed);
if (rlen > 0) {
float score = (_radio && !is_tx) ? _radio->packetScore(snr, rlen) : NAN;
len = MQTTMessageBuilder::buildPacketJSONFromRaw(
_packet_json_doc,
reconstructed, rlen, packet, is_tx, _origin, origin_id,
snr, rssi, _timezone, active_buffer, active_buffer_size
snr, rssi, score, _timezone, active_buffer, active_buffer_size
);
} else {
len = MQTTMessageBuilder::buildPacketJSON(