From fcae0502184337133a30201a1c941bee5550b9c9 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Tue, 21 May 2024 11:25:56 +0200 Subject: [PATCH] feat: more Zniffer polishing (#6863) --- docs/api/zniffer.md | 27 +++++++++- packages/core/src/capabilities/Protocols.ts | 35 ++++++++---- packages/core/src/consts/index.ts | 3 ++ packages/zwave-js/src/lib/zniffer/MPDU.ts | 56 +++++++++++++++++--- packages/zwave-js/src/lib/zniffer/Zniffer.ts | 9 ++++ packages/zwave-js/src/lib/zniffer/_Types.ts | 4 +- 6 files changed, 114 insertions(+), 20 deletions(-) diff --git a/docs/api/zniffer.md b/docs/api/zniffer.md index 9609a0045551..b655042e3b99 100644 --- a/docs/api/zniffer.md +++ b/docs/api/zniffer.md @@ -46,6 +46,9 @@ interface ZnifferOptions { * Supported regions and their names have to be queried using the `getFrequencies` and `getFrequencyInfo(frequency)` commands. */ defaultFrequency?: number; + + /** Limit the number of frames that are kept in memory. */ + maxCapturedFrames?: number; } ``` @@ -250,6 +253,14 @@ type ZWaveFrame = ) > ) + // Broadcast frame. This is technically a singlecast frame, + // but the destination node ID is always 255 and it is not routed + | { + type: ZWaveFrameType.Broadcast; + destinationNodeId: typeof NODE_ID_BROADCAST; + ackRequested: boolean; + payload: Buffer | CommandClass; + } | { // Multicast frame, not routed type: ZWaveFrameType.Multicast; @@ -275,6 +286,7 @@ type ZWaveFrame = } | { type: ZWaveFrameType.ExplorerInclusionRequest; networkHomeId: number; + payload: Buffer | CommandClass; }) // Common fields for all explorer frames & { @@ -313,12 +325,21 @@ type LongRangeFrame = } // Different kinds of Long Range frames: & ( - { + | { // Singlecast frame type: LongRangeFrameType.Singlecast; ackRequested: boolean; payload: Buffer | CommandClass; - } | { + } + | { + // Broadcast frame. This is technically a singlecast frame, + // but the destination node ID is always 4095 + type: LongRangeFrameType.Broadcast; + destinationNodeId: typeof NODE_ID_BROADCAST_LR; + ackRequested: boolean; + payload: Buffer | CommandClass; + } + | { // Acknowledgement frame type: LongRangeFrameType.Ack; incomingRSSI: RSSI; @@ -401,6 +422,7 @@ enum ZWaveFrameType { ExplorerInclusionRequest, BeamStart, BeamStop, + Broadcast, } ``` @@ -412,6 +434,7 @@ enum LongRangeFrameType { Ack, BeamStart, BeamStop, + Broadcast, } ``` diff --git a/packages/core/src/capabilities/Protocols.ts b/packages/core/src/capabilities/Protocols.ts index 1d1e3733d8cb..5b49c958727e 100644 --- a/packages/core/src/capabilities/Protocols.ts +++ b/packages/core/src/capabilities/Protocols.ts @@ -62,18 +62,35 @@ export enum ZnifferProtocolDataRate { LongRange_100k = 0x03, } +/** + * Converts a ZnifferProtocolDataRate into a human-readable string. + * @param includeProtocol - Whether to include the protocol name in the output + */ export function znifferProtocolDataRateToString( rate: ZnifferProtocolDataRate, + includeProtocol: boolean = true, ): string { - switch (rate) { - case ZnifferProtocolDataRate.ZWave_9k6: - return "Z-Wave, 9.6 kbit/s"; - case ZnifferProtocolDataRate.ZWave_40k: - return "Z-Wave, 40 kbit/s"; - case ZnifferProtocolDataRate.ZWave_100k: - return "Z-Wave, 100 kbit/s"; - case ZnifferProtocolDataRate.LongRange_100k: - return "Z-Wave Long Range, 100 kbit/s"; + if (includeProtocol) { + switch (rate) { + case ZnifferProtocolDataRate.ZWave_9k6: + return "Z-Wave, 9.6 kbit/s"; + case ZnifferProtocolDataRate.ZWave_40k: + return "Z-Wave, 40 kbit/s"; + case ZnifferProtocolDataRate.ZWave_100k: + return "Z-Wave, 100 kbit/s"; + case ZnifferProtocolDataRate.LongRange_100k: + return "Z-Wave Long Range, 100 kbit/s"; + } + } else { + switch (rate) { + case ZnifferProtocolDataRate.ZWave_9k6: + return "9.6 kbit/s"; + case ZnifferProtocolDataRate.ZWave_40k: + return "40 kbit/s"; + case ZnifferProtocolDataRate.ZWave_100k: + case ZnifferProtocolDataRate.LongRange_100k: + return "100 kbit/s"; + } } return `Unknown (${num2hex(rate)})`; } diff --git a/packages/core/src/consts/index.ts b/packages/core/src/consts/index.ts index 851b37ef51ff..90c6f1c1ac78 100644 --- a/packages/core/src/consts/index.ts +++ b/packages/core/src/consts/index.ts @@ -7,6 +7,9 @@ export const MAX_NODES_LR = 4000; // FIXME: This seems too even, figure out the /** The broadcast target node id */ export const NODE_ID_BROADCAST = 0xff; +/** The broadcast target node id for Z-Wave LR */ +export const NODE_ID_BROADCAST_LR = 0xfff; + /** The highest allowed node id */ export const NODE_ID_MAX = MAX_NODES; diff --git a/packages/zwave-js/src/lib/zniffer/MPDU.ts b/packages/zwave-js/src/lib/zniffer/MPDU.ts index b4a751a399a8..9c45159e2c5e 100644 --- a/packages/zwave-js/src/lib/zniffer/MPDU.ts +++ b/packages/zwave-js/src/lib/zniffer/MPDU.ts @@ -4,6 +4,8 @@ import { MPDUHeaderType, type MessageOrCCLogEntry, type MessageRecord, + NODE_ID_BROADCAST, + NODE_ID_BROADCAST_LR, Protocols, type RSSI, ZWaveError, @@ -906,6 +908,7 @@ export class BeamStop { } } +/** An application-oriented representation of a Z-Wave frame that was captured by the Zniffer */ export type ZWaveFrame = // Common fields for all Z-Wave frames & { @@ -965,6 +968,14 @@ export type ZWaveFrame = ) > ) + // Broadcast frame. This is technically a singlecast frame, + // but the destination node ID is always 255 and it is not routed + | { + type: ZWaveFrameType.Broadcast; + destinationNodeId: typeof NODE_ID_BROADCAST; + ackRequested: boolean; + payload: Buffer | CommandClass; + } | { // Multicast frame, not routed type: ZWaveFrameType.Multicast; @@ -1025,12 +1036,21 @@ export type LongRangeFrame = } // Different kinds of Long Range frames: & ( - { + | { // Singlecast frame type: LongRangeFrameType.Singlecast; ackRequested: boolean; payload: Buffer | CommandClass; - } | { + } + | { + // Broadcast frame. This is technically a singlecast frame, + // but the destination node ID is always 4095 + type: LongRangeFrameType.Broadcast; + destinationNodeId: typeof NODE_ID_BROADCAST_LR; + ackRequested: boolean; + payload: Buffer | CommandClass; + } + | { // Acknowledgement frame type: LongRangeFrameType.Ack; incomingRSSI: RSSI; @@ -1136,13 +1156,24 @@ export function mpduToZWaveFrame( }; if (mpdu instanceof SinglecastZWaveMPDU) { - return { - type: ZWaveFrameType.Singlecast, + const ret = { ...retBase, - destinationNodeId: mpdu.destinationNodeId, ackRequested: mpdu.ackRequested, payload: payloadCC ?? mpdu.payload, }; + if (mpdu.destinationNodeId === NODE_ID_BROADCAST) { + return { + type: ZWaveFrameType.Broadcast, + destinationNodeId: mpdu.destinationNodeId, + ...ret, + }; + } else { + return { + type: ZWaveFrameType.Singlecast, + destinationNodeId: mpdu.destinationNodeId, + ...ret, + }; + } } else if (mpdu instanceof AckZWaveMPDU) { return { type: ZWaveFrameType.AckDirect, @@ -1235,12 +1266,23 @@ export function mpduToLongRangeFrame( }; if (mpdu instanceof SinglecastLongRangeMPDU) { - return { + const ret = { ...retBase, - type: LongRangeFrameType.Singlecast, ackRequested: mpdu.ackRequested, payload: payloadCC ?? mpdu.payload, }; + if (mpdu.destinationNodeId === NODE_ID_BROADCAST_LR) { + return { + type: LongRangeFrameType.Broadcast, + ...ret, + destinationNodeId: mpdu.destinationNodeId, // Make TS happy + }; + } else { + return { + type: LongRangeFrameType.Singlecast, + ...ret, + }; + } } else if (mpdu instanceof AckLongRangeMPDU) { return { type: LongRangeFrameType.Ack, diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 112ce48e3ece..5b1d725bd91f 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -127,6 +127,9 @@ export interface ZnifferOptions { * Supported regions and their names have to be queried using the `getFrequencies` and `getFrequencyInfo(frequency)` commands. */ defaultFrequency?: number; + + /** Limit the number of frames that are kept in memory. */ + maxCapturedFrames?: number; } function is700PlusSeries( @@ -399,6 +402,12 @@ export class Zniffer extends TypedEventEmitter { frameData: dataMsg.payload, }; this._capturedFrames.push(capture); + if ( + this._options.maxCapturedFrames != undefined + && this._capturedFrames.length > this._options.maxCapturedFrames + ) { + this._capturedFrames.shift(); + } this.handleDataMessage(dataMsg, capture); } } diff --git a/packages/zwave-js/src/lib/zniffer/_Types.ts b/packages/zwave-js/src/lib/zniffer/_Types.ts index 88d34d11f587..ad52cdf7ee9a 100644 --- a/packages/zwave-js/src/lib/zniffer/_Types.ts +++ b/packages/zwave-js/src/lib/zniffer/_Types.ts @@ -13,13 +13,13 @@ export enum ZWaveFrameType { ExplorerInclusionRequest, BeamStart, BeamStop, + Broadcast, } -/** An application-oriented representation of a Z-Wave frame that was captured by the Zniffer */ - export enum LongRangeFrameType { Singlecast, Ack, BeamStart, BeamStop, + Broadcast, }