diff --git a/wirepas-5g-mesh-gateway/ScannableArray.ts b/wirepas-5g-mesh-gateway/ScannableArray.ts index 332fad0..46890e1 100644 --- a/wirepas-5g-mesh-gateway/ScannableArray.ts +++ b/wirepas-5g-mesh-gateway/ScannableArray.ts @@ -34,4 +34,8 @@ export class ScannableArray { hasNext(): boolean { return this.array.at(this.index + 1) !== undefined } + + pos(): number { + return this.index + } } diff --git a/wirepas-5g-mesh-gateway/decodePayload.spec.ts b/wirepas-5g-mesh-gateway/decodePayload.spec.ts index 1d06e2a..39ed11e 100644 --- a/wirepas-5g-mesh-gateway/decodePayload.spec.ts +++ b/wirepas-5g-mesh-gateway/decodePayload.spec.ts @@ -55,20 +55,14 @@ void describe('decodePayload()', () => { const decoded = decodePayload(payload) assert.deepEqual(decoded, { - counter: 49730, - timestamp: 251355997789000, // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - temperature: 24.479999542236328, - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - humidity: 17.695999145507812, - raw_pressure: 100325, - raw_gas: 81300, + temp: 24.479999542236328, }) }) void it('should decode a button press', () => assert.deepEqual(decodePayload(Buffer.from('010002', 'hex')), { - button: 2, + btn: 2, })) void it('should decode a LED state change', () => diff --git a/wirepas-5g-mesh-gateway/decodePayload.ts b/wirepas-5g-mesh-gateway/decodePayload.ts index b50e73c..43a59d7 100644 --- a/wirepas-5g-mesh-gateway/decodePayload.ts +++ b/wirepas-5g-mesh-gateway/decodePayload.ts @@ -1,13 +1,7 @@ import { ScannableArray } from './ScannableArray.js' export type Wirepas5GMeshNodePayload = { - counter?: number - // Uptime in nanoseconds - timestamp?: number // e.g. 251355997789000 / 1000 / 1000 / 1000 / 60 / 60 / 24 = 2.909 days - temperature?: number - button?: number - humidity?: number - raw_pressure?: number - raw_gas?: number + temp?: number + btn?: number led?: { r?: boolean g?: boolean @@ -17,6 +11,7 @@ export type Wirepas5GMeshNodePayload = { enum MessageType { COUNTER = 0x01, // [0x04] [size_t counter] + // Uptime in nanoseconds, e.g. 251355997789000 / 1000 / 1000 / 1000 / 60 / 60 / 24 = 2.909 days TIMESTAMP = 0x02, // [0x08] [int64_t timestamp] IAQ = 0x03, // [0x02] [uint16_t iaq] IAQ_ACC = 0x04, // [0x01] [uint8_t iaq_acc] @@ -70,6 +65,7 @@ Byte 3: State. 0x00: off, 0x01: on. */ export const decodePayload = ( payload: Uint8Array, + onUnknown?: (type: number, pos: number) => void, ): Wirepas5GMeshNodePayload => { const msg = new ScannableArray(payload) @@ -79,14 +75,14 @@ export const decodePayload = ( if (payload.length === 3 && msg.peek() === 1) { msg.next() // skip type msg.next() // skip len - return { button: msg.peek() } + return { btn: readUint(msg, 1) } } // LED special case if (payload.length === 3 && msg.peek() === 3) { msg.next() // skip type - const color = msg.getChar() - const state = msg.getChar() + const color = readUint(msg, 1) + const state = readUint(msg, 1) switch (color) { case LED_COLOR.BLUE: return { @@ -117,14 +113,9 @@ export const decodePayload = ( for (let i = 0; i < len; i++) msg.getChar() } switch (type) { - // Periodic message with a counter value + // Skip case MessageType.COUNTER: - message = { ...message, counter: readUint(msg, len) } - continue case MessageType.TIMESTAMP: - message = { ...message, timestamp: readUint(msg, len) } - continue - // Skip case MessageType.IAQ: case MessageType.IAQ_ACC: case MessageType.SIAQ: @@ -139,22 +130,16 @@ export const decodePayload = ( case MessageType.CO2_ACC: case MessageType.TEMP_RAW: case MessageType.HUM_RAW: - skip() - continue - case MessageType.TEMPERATURE: - message = { ...message, temperature: readFloat(msg, len) } - continue case MessageType.HUMIDITY: - message = { ...message, humidity: readFloat(msg, len) } - continue case MessageType.PRESS_RAW: - message = { ...message, raw_pressure: readFloat(msg, len) } - continue case MessageType.GAS_RAW: - message = { ...message, raw_gas: readFloat(msg, len) } + skip() + continue + case MessageType.TEMPERATURE: + message = { ...message, temp: readFloat(msg, len) } continue default: - console.error(`Unknown message type`, type) + onUnknown?.(type, msg.pos() - 1) skip() break } diff --git a/wirepas-5g-mesh-gateway/gateway.ts b/wirepas-5g-mesh-gateway/gateway.ts index 7b63f94..a240299 100644 --- a/wirepas-5g-mesh-gateway/gateway.ts +++ b/wirepas-5g-mesh-gateway/gateway.ts @@ -13,6 +13,7 @@ import { type ThingAttribute, } from '@aws-sdk/client-iot' import { merge } from 'lodash-es' +import { decodePayload } from './decodePayload.js' const { region, accessKeyId, secretAccessKey, gatewayEndpoint } = fromEnv({ region: 'GATEWAY_REGION', @@ -113,10 +114,24 @@ client.on('message', (_, message) => { nodes[gwId] = merge( { [sourceAddress]: { - travelTimeMs, + lat: travelTimeMs, ...(hopCount !== undefined ? { hops: hopCount } : {}), - rxTime, + ts: rxTime, qos, + payload: ((payload) => { + try { + return decodePayload(payload, (type, pos) => { + debug(`Unknown message type`, type) + debug(Buffer.from(payload).toString('hex')) + debug(' '.repeat(Math.max(0, pos - 1)) + ' ^') + }) + } catch { + debug( + `Failed to decode payload: ${Buffer.from(payload).toString('hex')}`, + ) + } + return {} + })(payload), }, }, nodes[gwId], @@ -127,8 +142,13 @@ client.on('message', (_, message) => { // Regularly send buffered updates setInterval(async () => { await Promise.all( - Object.entries(nodes).map(async ([gwId, nodes]) => - iotDataClient.send( + Object.entries(nodes).map(async ([gwId, nodes]) => { + Object.entries(nodes).forEach(([nodeId, data]) => { + debug() + debug(gwId, nodeId, JSON.stringify(data)) + }) + + return iotDataClient.send( new UpdateThingShadowCommand({ thingName: gwId, payload: JSON.stringify({ @@ -139,8 +159,8 @@ setInterval(async () => { }, }), }), - ), - ), + ) + }), ) nodes = {} }, stateFlushInterval * 1000)