feat(mqtt): add neighbors JSON payload builder (host-tested)

Add buildNeighborsMessage to the pure MQTTPayloadBuilder core and a thin
delegating wrapper + NeighborsMessageEntry alias on MQTTMessageBuilder, so
the neighbors topic is built by the same firmware-facing API as status/
packet/raw while the layout logic stays exercisable by native tests.

The document is bounded to the publish buffer: entries arrive ordered most-
to least-useful and the tail is dropped once the next entry would overflow,
so a fixed PSRAM buffer can never be handed truncated JSON.

Uses ArduinoJson v7 idioms (.to<JsonObject>()/.add<JsonObject>()) to stay
warning-clean under -Werror, unlike the deprecated createNested* forms.

Adds three test_mqtt_payload_builder cases: self+entry round-trip, empty
table / null scopes, and bounded-growth tail-drop under a tight buffer.
This commit is contained in:
agessaman committed 2026-07-19 22:39:41 -07:00
1 parent 8d7a47abf7
commit e36aee04d4
5 files changed
+182

No files matched your search

+16
View File
@@ -102,6 +102,22 @@ int MQTTMessageBuilder::buildRawMessage(
origin, origin_id, timestamp, raw, buffer, buffer_size);
}
int MQTTMessageBuilder::buildNeighborsMessage(
JsonDocument& doc,
const char* origin,
const char* origin_id,
const char* timestamp,
const char* self_scopes,
const NeighborsMessageEntry* neighbors,
int neighbor_count,
char* buffer,
size_t buffer_size
) {
return MQTTPayloadBuilder::buildNeighborsMessage(
doc, origin, origin_id, timestamp, self_scopes, neighbors, neighbor_count,
buffer, buffer_size);
}
int MQTTMessageBuilder::buildPacketJSON(
JsonDocument& doc,
mesh::Packet* packet,
+17
View File
@@ -2,6 +2,7 @@
#include "MeshCore.h"
#include <ArduinoJson.h>
#include "MQTTPayloadBuilder.h"
#include <Mesh.h>
#include <Timezone.h>
@@ -153,6 +154,22 @@ public:
size_t buffer_size
);
// Neighbors table entry + JSON builder. The layout logic lives in the pure,
// host-tested MQTTPayloadBuilder; this is the firmware-facing alias/delegate,
// matching the status/packet/raw builders.
using NeighborsMessageEntry = MQTTPayloadBuilder::NeighborsMessageEntry;
static int buildNeighborsMessage(
JsonDocument& doc,
const char* origin,
const char* origin_id,
const char* timestamp,
const char* self_scopes,
const NeighborsMessageEntry* neighbors,
int neighbor_count,
char* buffer,
size_t buffer_size
);
/**
* Convert packet to JSON message
*
+45
View File
@@ -184,3 +184,48 @@ int MQTTPayloadBuilder::buildRawMessage(
return serializeComplete(root, buffer, buffer_size);
}
int MQTTPayloadBuilder::buildNeighborsMessage(
JsonDocument& doc,
const char* origin,
const char* origin_id,
const char* timestamp,
const char* self_scopes,
const NeighborsMessageEntry* neighbors,
int neighbor_count,
char* buffer,
size_t buffer_size
) {
if (!buffer || buffer_size == 0) return 0;
doc.clear();
JsonObject root = doc.to<JsonObject>();
root["timestamp"] = timestamp;
root["origin"] = origin;
root["origin_id"] = origin_id;
JsonObject self = root["self"].to<JsonObject>();
self["scopes"] = self_scopes ? self_scopes : "";
JsonArray arr = root["neighbors"].to<JsonArray>();
if (measureJson(root) >= buffer_size) return 0;
for (int i = 0; i < neighbor_count; i++) {
JsonObject nb = arr.add<JsonObject>();
nb["pubkey"] = neighbors[i].pubkey_hex;
nb["snr"] = neighbors[i].snr;
nb["heard_secs_ago"] = neighbors[i].heard_secs_ago;
nb["scopes"] = neighbors[i].scopes ? neighbors[i].scopes : "";
nb["status"] = neighbors[i].status;
// Entries arrive ordered most- to least-useful. Stop as soon as the next
// one would fill the fixed publish buffer, dropping the remaining tail so
// document growth stays bounded.
if (measureJson(root) >= buffer_size) {
arr.remove(arr.size() - 1);
break;
}
}
return serializeComplete(root, buffer, buffer_size);
}
+23
View File
@@ -68,5 +68,28 @@ public:
char* buffer,
size_t buffer_size
);
struct NeighborsMessageEntry {
const char* pubkey_hex;
float snr;
uint32_t heard_secs_ago;
const char* scopes;
const char* status;
};
// Build neighbors-table JSON for the meshcore/{iata}/{device}/neighbors topic.
// Callers order entries most- to least-useful; document growth is bounded to
// buffer_size and the remaining tail is dropped once the next entry won't fit.
static int buildNeighborsMessage(
JsonDocument& doc,
const char* origin,
const char* origin_id,
const char* timestamp,
const char* self_scopes,
const NeighborsMessageEntry* neighbors,
int neighbor_count,
char* buffer,
size_t buffer_size
);
};