mesh: update node batteryLevel from live NODEINFO_APP packets
battery_level was only populated during the initial sync; NodeInfo frames streamed as NODEINFO_APP (port 4) now refresh deviceMetrics in the node database so /api/nodes and downstream consumers see current values between syncs. decodeNodeInfo + PORT_NODEINFO_APP added to the codec, one branch in the existing packet-handling path.
This commit is contained in:
@@ -44,6 +44,36 @@ function positionPacketFrame(
|
||||
);
|
||||
}
|
||||
|
||||
// live node-info update: NODEINFO_APP packet with a serialized NodeInfo in Data.payload
|
||||
function nodeInfoPacketFrame(
|
||||
from: number,
|
||||
fields: {
|
||||
user?: { id?: string; longName?: string };
|
||||
deviceMetrics?: { batteryLevel?: number };
|
||||
},
|
||||
): Uint8Array {
|
||||
const data = create(P.Mesh.DataSchema, {
|
||||
portnum: P.Portnums.PortNum.NODEINFO_APP,
|
||||
payload: toBinary(
|
||||
P.Mesh.NodeInfoSchema,
|
||||
create(P.Mesh.NodeInfoSchema, { num: from, ...fields }),
|
||||
),
|
||||
});
|
||||
const pkt = create(P.Mesh.MeshPacketSchema, {
|
||||
from,
|
||||
id: 1,
|
||||
payloadVariant: { case: 'decoded', value: data },
|
||||
});
|
||||
return frame(
|
||||
toBinary(
|
||||
P.Mesh.FromRadioSchema,
|
||||
create(P.Mesh.FromRadioSchema, {
|
||||
payloadVariant: { case: 'packet', value: pkt },
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// inbound text message: TEXT_MESSAGE_APP packet with UTF-8 in Data.payload
|
||||
function textPacketFrame(
|
||||
from: number,
|
||||
@@ -418,6 +448,47 @@ describe('MeshService node database', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('updates batteryLevel from live NODEINFO_APP packets (and stubs unknown nodes)', async () => {
|
||||
const bridge = await startBridge();
|
||||
process.env.MESH_HOST = '127.0.0.1';
|
||||
process.env.MESH_PORT = String(bridge.port);
|
||||
const svc = new MeshService();
|
||||
try {
|
||||
svc.onModuleInit();
|
||||
await waitFor(
|
||||
() => bridge.sockets.length === 1 && svc.status().connected,
|
||||
);
|
||||
|
||||
// sync entry with a stale battery, then a live NODEINFO_APP update
|
||||
bridge.sockets[0].write(nodeInfoFrame(KAMIL_NUM, kamilNodeInfo()));
|
||||
await waitFor(async () => (await svc.nodes()).length === 1);
|
||||
expect((await svc.nodes())[0]?.batteryLevel).toBe(87);
|
||||
|
||||
bridge.sockets[0].write(
|
||||
nodeInfoPacketFrame(KAMIL_NUM, { deviceMetrics: { batteryLevel: 9 } }),
|
||||
);
|
||||
|
||||
await waitFor(async () => (await svc.nodes())[0]?.batteryLevel === 9);
|
||||
// other fields of the sync entry survive the live update
|
||||
expect(await svc.node(KAMIL_NUM)).toMatchObject({
|
||||
longName: 'Kamil SP8MK/P',
|
||||
shortName: 'KK',
|
||||
batteryLevel: 9,
|
||||
});
|
||||
|
||||
// unknown node: a NODEINFO_APP with deviceMetrics creates a stub entry
|
||||
bridge.sockets[0].write(
|
||||
nodeInfoPacketFrame(555, { deviceMetrics: { batteryLevel: 3 } }),
|
||||
);
|
||||
|
||||
await waitFor(async () => (await svc.nodes()).length === 2);
|
||||
expect((await svc.node(555))?.batteryLevel).toBe(3);
|
||||
} finally {
|
||||
svc.onModuleDestroy();
|
||||
bridge.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('looks up nodes by exact num, numeric string, exact name and unique substring', async () => {
|
||||
const bridge = await startBridge();
|
||||
process.env.MESH_HOST = '127.0.0.1';
|
||||
|
||||
Reference in New Issue
Block a user