fix(mqtt): accept official firmware neighbours payload shape (UK envelope + snr_db/heard_secs_ago)

This commit is contained in:
gadgethd committed 2026-08-07 11:42:11 +00:00
1 parent c122762df9
commit 8bc4b0dce6
3 files changed
+15 -2

No files matched your search

+6
View File
@@ -8,6 +8,12 @@ test('extracts the raw neighbor array, including an empty sample', () => {
assert.deepEqual(extractNeighborNodes({ nodes: [] }), []);
});
test('accepts the official firmware UK-spelling envelope', () => {
const neighbours = [{ id: '5F318F39', snr_db: -4.5, heard_secs_ago: 3863 }];
assert.deepEqual(extractNeighborNodes({ neighbours }), neighbours);
assert.deepEqual(extractNeighborNodes({ neighbours: [] }), []);
});
test('rejects malformed neighbor envelopes', () => {
assert.equal(extractNeighborNodes(null), null);
assert.equal(extractNeighborNodes([]), null);
+4 -1
View File
@@ -1,5 +1,8 @@
export function extractNeighborNodes(value: unknown): unknown[] | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
const nodes = (value as Record<string, unknown>)['nodes'];
const record = value as Record<string, unknown>;
// Official MeshCore firmware publishes the UK spelling "neighbours" (matching
// its /neighbours topic suffix); our MQTT fork publishes "nodes". Accept both.
const nodes = record['nodes'] ?? record['neighbours'];
return Array.isArray(nodes) ? nodes : null;
}
+5 -1
View File
@@ -637,11 +637,15 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) {
CASE
WHEN jsonb_typeof(item->'snr') = 'number' THEN (item->>'snr')::double precision
WHEN jsonb_typeof(item->'SNR') = 'number' THEN (item->>'SNR')::double precision
WHEN jsonb_typeof(item->'snr_db') = 'number' THEN (item->>'snr_db')::double precision
ELSE NULL
END AS snr,
COALESCE(
CASE WHEN jsonb_typeof(item->'last_seen') IN ('string', 'number') THEN item->>'last_seen' ELSE NULL END,
CASE WHEN jsonb_typeof(item->'lastSeen') IN ('string', 'number') THEN item->>'lastSeen' ELSE NULL END
CASE WHEN jsonb_typeof(item->'lastSeen') IN ('string', 'number') THEN item->>'lastSeen' ELSE NULL END,
CASE WHEN jsonb_typeof(item->'heard_secs_ago') = 'number'
THEN (latest.time - ((item->>'heard_secs_ago')::double precision * INTERVAL '1 second'))::text
ELSE NULL END
) AS last_seen,
latest.time::text AS sample_time
FROM latest