Skip to content

Commit 6323685

Browse files
raman325claude
andcommitted
feat: add schema 47 zniffer, utility, and broadcast commands (4/4)
Add zniffer Long Range and file I/O commands, utility firmware commands, and broadcast node Long Range support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0dde50c commit 6323685

12 files changed

Lines changed: 291 additions & 12 deletions

API_SCHEMA.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,7 @@ Base schema.
131131
- Added node event: `check link reliability progress`
132132
- Added controller events: `joining network show dsk`, `joining network done`
133133
- Added endpoint commands: `get_ccs`, `may_support_basic_cc`, `was_cc_removed_via_config`
134+
- Added broadcast node Long Range support via `longRange` flag on `broadcast_node` commands
135+
- Added zniffer Long Range commands: `get_lr_regions`, `get_current_lr_channel_config`, `get_supported_lr_channel_configs`, `set_lr_channel_config`
136+
- Added zniffer file I/O commands: `save_capture_to_file`, `load_capture_from_file`, `load_capture_from_buffer`
137+
- Added utility commands: `guess_firmware_file_format`, `try_unzip_firmware_file`, `extract_firmware`

README.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,9 @@ interface {
14891489

14901490
### Multicasting
14911491

1492-
There are several commands available that can be multicast to multiple nodes simultaneously. If you would like to broadcast to all nodes, use the `broadcast_node` prefix for the following commands. If you would like to multicast to a subset of nodes, use the `multicast_group` prefix for the following commands, adding a `nodeIDs` list as an input parameter:
1492+
There are several commands available that can be multicast to multiple nodes simultaneously. If you would like to broadcast to all nodes, use the `broadcast_node` prefix for the following commands. If you would like to multicast to a subset of nodes, use the `multicast_group` prefix for the following commands, adding a `nodeIDs` list as an input parameter.
1493+
1494+
> NOTE: For `broadcast_node` commands, you can pass a `longRange` boolean flag to broadcast to Long Range nodes instead of classic Z-Wave nodes.
14931495
14941496
```ts
14951497
interface IncomingCommandMulticastGroupBase extends IncomingCommandBase {
@@ -1757,6 +1759,101 @@ interface {
17571759
}
17581760
```
17591761

1762+
#### Get Long Range regions
1763+
1764+
Gets list of Long Range capable regions.
1765+
1766+
[compatible with schema version: 47+]
1767+
1768+
```ts
1769+
interface {
1770+
messageId: string;
1771+
command: "zniffer.get_lr_regions";
1772+
}
1773+
```
1774+
1775+
#### Get current Long Range channel config
1776+
1777+
Gets currently configured Long Range channel configuration.
1778+
1779+
[compatible with schema version: 47+]
1780+
1781+
```ts
1782+
interface {
1783+
messageId: string;
1784+
command: "zniffer.get_current_lr_channel_config";
1785+
}
1786+
```
1787+
1788+
#### Get supported Long Range channel configs
1789+
1790+
Gets map of supported Long Range channel configurations.
1791+
1792+
[compatible with schema version: 47+]
1793+
1794+
```ts
1795+
interface {
1796+
messageId: string;
1797+
command: "zniffer.get_supported_lr_channel_configs";
1798+
}
1799+
```
1800+
1801+
#### Set Long Range channel config
1802+
1803+
Set Long Range channel configuration (800 series only).
1804+
1805+
[compatible with schema version: 47+]
1806+
1807+
```ts
1808+
interface {
1809+
messageId: string;
1810+
command: "zniffer.set_lr_channel_config";
1811+
channelConfig: number;
1812+
}
1813+
```
1814+
1815+
#### [Save capture to file](https://zwave-js.github.io/node-zwave-js/#/api/zniffer?id=capturing-frames)
1816+
1817+
Saves the captured frames to a `.zlf` file that can be read by the official Zniffer application.
1818+
1819+
[compatible with schema version: 47+]
1820+
1821+
```ts
1822+
interface {
1823+
messageId: string;
1824+
command: "zniffer.save_capture_to_file";
1825+
filePath: string;
1826+
}
1827+
```
1828+
1829+
#### [Load capture from file](https://zwave-js.github.io/node-zwave-js/#/api/zniffer?id=capturing-frames)
1830+
1831+
Loads previously saved captured frames from a `.zlf` file.
1832+
1833+
[compatible with schema version: 47+]
1834+
1835+
```ts
1836+
interface {
1837+
messageId: string;
1838+
command: "zniffer.load_capture_from_file";
1839+
filePath: string;
1840+
}
1841+
```
1842+
1843+
#### [Load capture from buffer](https://zwave-js.github.io/node-zwave-js/#/api/zniffer?id=capturing-frames)
1844+
1845+
Loads captured frames from a base64 encoded buffer.
1846+
1847+
[compatible with schema version: 47+]
1848+
1849+
```ts
1850+
interface {
1851+
messageId: string;
1852+
command: "zniffer.load_capture_from_buffer";
1853+
data: string; // base64 encoded .zlf data
1854+
}
1855+
```
1856+
17601857
## Events
17611858

17621859
### `zwave-js` Events

src/lib/broadcast_node/incoming_message.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { SetValueAPIOptions } from "zwave-js";
33
import { IncomingCommandBase } from "../incoming_message_base.js";
44
import { BroadcastNodeCommand } from "./command.js";
55

6-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
7-
export interface IncomingCommandBroadcastNodeBase extends IncomingCommandBase {}
6+
export interface IncomingCommandBroadcastNodeBase extends IncomingCommandBase {
7+
longRange?: boolean;
8+
}
89

910
export interface IncomingCommandBroadcastNodeSetValue
1011
extends IncomingCommandBroadcastNodeBase {

src/lib/broadcast_node/message_handler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export class BroadcastNodeMessageHandler implements MessageHandler {
1818
): Promise<BroadcastNodeResultTypes[BroadcastNodeCommand]> {
1919
const { command } = message;
2020

21-
const virtualNode = this.driver.controller.getBroadcastNode();
21+
const virtualNode = message.longRange
22+
? this.driver.controller.getBroadcastNodeLR()
23+
: this.driver.controller.getBroadcastNode();
2224

2325
switch (message.command) {
2426
case BroadcastNodeCommand.setValue: {

src/lib/utils/command.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ export enum UtilsCommand {
66
buffer2hex = "utils.buffer2hex", // While made available in the server due to its availability within the driver, this functionality works best when implemented locally
77
getEnumMemberName = "utils.get_enum_member_name", // While made available in the server due to its availability within the driver, this functionality works best when implemented locally
88
rssiToString = "utils.rssi_to_string",
9+
guessFirmwareFileFormat = "utils.guess_firmware_file_format",
10+
tryUnzipFirmwareFile = "utils.try_unzip_firmware_file",
11+
extractFirmware = "utils.extract_firmware",
912
}

src/lib/utils/incoming_message.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RSSI } from "zwave-js";
2+
import { FirmwareFileFormat } from "@zwave-js/core";
23
import { IncomingCommandBase } from "../incoming_message_base.js";
34
import { UtilsCommand } from "./command.js";
45

@@ -48,11 +49,34 @@ export interface IncomingCommandUtilsRssiToString
4849
rssi: RSSI;
4950
}
5051

52+
export interface IncomingCommandUtilsGuessFirmwareFileFormat
53+
extends IncomingCommandUtilsBase {
54+
command: UtilsCommand.guessFirmwareFileFormat;
55+
filename: string;
56+
file: string; // base64 encoded
57+
}
58+
59+
export interface IncomingCommandUtilsTryUnzipFirmwareFile
60+
extends IncomingCommandUtilsBase {
61+
command: UtilsCommand.tryUnzipFirmwareFile;
62+
file: string; // base64 encoded ZIP file
63+
}
64+
65+
export interface IncomingCommandUtilsExtractFirmware
66+
extends IncomingCommandUtilsBase {
67+
command: UtilsCommand.extractFirmware;
68+
file: string; // base64 encoded firmware file
69+
format: FirmwareFileFormat;
70+
}
71+
5172
export type IncomingMessageUtils =
5273
| IncomingCommandUtilsParseQRCodeString
5374
| IncomingCommandUtilsTryParseDSKFromQRCodeString
5475
| IncomingCommandUtilsNum2hex
5576
| IncomingCommandUtilsFormatId
5677
| IncomingCommandUtilsBuffer2hex
5778
| IncomingCommandUtilsGetEnumMemberName
58-
| IncomingCommandUtilsRssiToString;
79+
| IncomingCommandUtilsRssiToString
80+
| IncomingCommandUtilsGuessFirmwareFileFormat
81+
| IncomingCommandUtilsTryUnzipFirmwareFile
82+
| IncomingCommandUtilsExtractFirmware;

src/lib/utils/message_handler.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import {
55
num2hex,
66
rssiToString,
77
} from "zwave-js";
8-
import { parseQRCodeString, tryParseDSKFromQRCodeString } from "@zwave-js/core";
8+
import {
9+
extractFirmware,
10+
guessFirmwareFileFormat,
11+
parseQRCodeString,
12+
tryParseDSKFromQRCodeString,
13+
tryUnzipFirmwareFile,
14+
} from "@zwave-js/core";
915
import { UnknownCommandError } from "../error.js";
1016
import { UtilsCommand } from "./command.js";
1117
import { IncomingMessageUtils } from "./incoming_message.js";
@@ -36,10 +42,7 @@ export class UtilsMessageHandler implements MessageHandler {
3642
return { id };
3743
}
3844
case UtilsCommand.buffer2hex: {
39-
const hex = buffer2hex(
40-
Uint8Array.from(message.buffer),
41-
message.uppercase,
42-
);
45+
const hex = buffer2hex(message.buffer, message.uppercase);
4346
return { hex };
4447
}
4548
case UtilsCommand.getEnumMemberName: {
@@ -50,6 +53,35 @@ export class UtilsMessageHandler implements MessageHandler {
5053
const rssi = rssiToString(message.rssi);
5154
return { rssi };
5255
}
56+
case UtilsCommand.guessFirmwareFileFormat: {
57+
const fileBuffer = Buffer.from(message.file, "base64");
58+
const format = guessFirmwareFileFormat(message.filename, fileBuffer);
59+
return { format };
60+
}
61+
case UtilsCommand.tryUnzipFirmwareFile: {
62+
const zipBuffer = Buffer.from(message.file, "base64");
63+
const result = tryUnzipFirmwareFile(zipBuffer);
64+
if (!result) {
65+
return { file: undefined };
66+
}
67+
return {
68+
file: {
69+
filename: result.filename,
70+
format: result.format,
71+
data: Buffer.from(result.rawData).toString("base64"),
72+
},
73+
};
74+
}
75+
case UtilsCommand.extractFirmware: {
76+
const fileBuffer = Buffer.from(message.file, "base64");
77+
const firmware = await extractFirmware(fileBuffer, message.format);
78+
return {
79+
firmware: {
80+
data: Buffer.from(firmware.data).toString("base64"),
81+
firmwareTarget: firmware.firmwareTarget,
82+
},
83+
};
84+
}
5385
default: {
5486
throw new UnknownCommandError(command);
5587
}

src/lib/utils/outgoing_message.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QRProvisioningInformation } from "@zwave-js/core";
1+
import { FirmwareFileFormat, QRProvisioningInformation } from "@zwave-js/core";
22
import { UtilsCommand } from "./command.js";
33

44
export interface UtilsResultTypes {
@@ -13,4 +13,21 @@ export interface UtilsResultTypes {
1313
[UtilsCommand.buffer2hex]: { hex: string };
1414
[UtilsCommand.getEnumMemberName]: { name: string };
1515
[UtilsCommand.rssiToString]: { rssi: string };
16+
[UtilsCommand.guessFirmwareFileFormat]: { format: FirmwareFileFormat };
17+
[UtilsCommand.tryUnzipFirmwareFile]: {
18+
// Returns undefined if not a valid ZIP or no compatible firmware found
19+
file:
20+
| {
21+
filename: string;
22+
format: FirmwareFileFormat;
23+
data: string; // base64 encoded
24+
}
25+
| undefined;
26+
};
27+
[UtilsCommand.extractFirmware]: {
28+
firmware: {
29+
data: string; // base64 encoded
30+
firmwareTarget?: number;
31+
};
32+
};
1633
}

src/lib/zniffer/command.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,16 @@ export enum ZnifferCommand {
99
supportedFrequencies = "zniffer.supported_frequencies",
1010
currentFrequency = "zniffer.current_frequency",
1111
setFrequency = "zniffer.set_frequency",
12+
// Undocumented
13+
getLRRegions = "zniffer.get_lr_regions",
14+
// Undocumented
15+
getCurrentLRChannelConfig = "zniffer.get_current_lr_channel_config",
16+
// Undocumented
17+
getSupportedLRChannelConfigs = "zniffer.get_supported_lr_channel_configs",
18+
// Undocumented
19+
setLRChannelConfig = "zniffer.set_lr_channel_config",
20+
// File I/O
21+
saveCaptureToFile = "zniffer.save_capture_to_file",
22+
loadCaptureFromFile = "zniffer.load_capture_from_file",
23+
loadCaptureFromBuffer = "zniffer.load_capture_from_buffer",
1224
}

src/lib/zniffer/incoming_message.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,47 @@ export interface IncomingCommandZnifferSetFrequency
5656
frequency: number;
5757
}
5858

59+
// Long Range
60+
export interface IncomingCommandZnifferGetLRRegions
61+
extends IncomingCommandZnifferBase {
62+
command: ZnifferCommand.getLRRegions;
63+
}
64+
65+
export interface IncomingCommandZnifferGetCurrentLRChannelConfig
66+
extends IncomingCommandZnifferBase {
67+
command: ZnifferCommand.getCurrentLRChannelConfig;
68+
}
69+
70+
export interface IncomingCommandZnifferGetSupportedLRChannelConfigs
71+
extends IncomingCommandZnifferBase {
72+
command: ZnifferCommand.getSupportedLRChannelConfigs;
73+
}
74+
75+
export interface IncomingCommandZnifferSetLRChannelConfig
76+
extends IncomingCommandZnifferBase {
77+
command: ZnifferCommand.setLRChannelConfig;
78+
channelConfig: number;
79+
}
80+
81+
// File I/O
82+
export interface IncomingCommandZnifferSaveCaptureToFile
83+
extends IncomingCommandZnifferBase {
84+
command: ZnifferCommand.saveCaptureToFile;
85+
filePath: string;
86+
}
87+
88+
export interface IncomingCommandZnifferLoadCaptureFromFile
89+
extends IncomingCommandZnifferBase {
90+
command: ZnifferCommand.loadCaptureFromFile;
91+
filePath: string;
92+
}
93+
94+
export interface IncomingCommandZnifferLoadCaptureFromBuffer
95+
extends IncomingCommandZnifferBase {
96+
command: ZnifferCommand.loadCaptureFromBuffer;
97+
data: string; // base64 encoded
98+
}
99+
59100
export type IncomingMessageZniffer =
60101
| IncomingCommandZnifferClearCapturedFrames
61102
| IncomingCommandZnifferGetCaptureAsZLFBuffer
@@ -66,4 +107,11 @@ export type IncomingMessageZniffer =
66107
| IncomingCommandZnifferSupportedFrequencies
67108
| IncomingCommandZnifferCurrentFrequency
68109
| IncomingCommandZnifferSetFrequency
69-
| IncomingCommandZnifferDestroy;
110+
| IncomingCommandZnifferDestroy
111+
| IncomingCommandZnifferGetLRRegions
112+
| IncomingCommandZnifferGetCurrentLRChannelConfig
113+
| IncomingCommandZnifferGetSupportedLRChannelConfigs
114+
| IncomingCommandZnifferSetLRChannelConfig
115+
| IncomingCommandZnifferSaveCaptureToFile
116+
| IncomingCommandZnifferLoadCaptureFromFile
117+
| IncomingCommandZnifferLoadCaptureFromBuffer;

0 commit comments

Comments
 (0)