From 8bc4b0dce6be530ab5d1d01fa1d86ac8ec1beb7d Mon Sep 17 00:00:00 2001 From: gadgethd <111318106+gadgethd@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:42:04 +0000 Subject: [PATCH] fix(mqtt): accept official firmware neighbours payload shape (UK envelope + snr_db/heard_secs_ago) --- backend/src/mqtt/neighborPayload.test.ts | 6 ++++++ backend/src/mqtt/neighborPayload.ts | 5 ++++- backend/src/owner/ownerRepository.ts | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/src/mqtt/neighborPayload.test.ts b/backend/src/mqtt/neighborPayload.test.ts index c953c05..a13c125 100644 --- a/backend/src/mqtt/neighborPayload.test.ts +++ b/backend/src/mqtt/neighborPayload.test.ts @@ -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); diff --git a/backend/src/mqtt/neighborPayload.ts b/backend/src/mqtt/neighborPayload.ts index 3529d90..7c405e2 100644 --- a/backend/src/mqtt/neighborPayload.ts +++ b/backend/src/mqtt/neighborPayload.ts @@ -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)['nodes']; + const record = value as Record; + // 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; } diff --git a/backend/src/owner/ownerRepository.ts b/backend/src/owner/ownerRepository.ts index c37fa71..8416c12 100644 --- a/backend/src/owner/ownerRepository.ts +++ b/backend/src/owner/ownerRepository.ts @@ -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