Skip to content

Commit 40e229e

Browse files
authored
Widen Node ID fields to allow for 16-bit Node Ids (#179)
1 parent 36515c8 commit 40e229e

78 files changed

Lines changed: 442 additions & 271 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/copilot-instructions.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Implements the Z-Wave Serial API frame-level protocol (as defined by the Z-Wave
6868
- **`ZWaveSerialPortCoordinator`** — Manages the serial port, frame send/receive channels, ACK handshake, and retransmission.
6969
- **`Commands/`** — Each Serial API command (e.g. `SendData`, `GetInitData`, `SoftReset`) is a struct implementing `ICommand<T>` (defined in `Serial/Commands/ICommand.cs`). Commands that expect a callback implement `IRequestWithCallback<T>`.
7070
- **`CommandId` enum** — All Serial API function IDs.
71+
- **`CommandDataParsingHelpers`** — Internal shared utilities: `ParseCommandClasses` (CC byte list → `CommandClassInfo` records) and `ParseNodeBitmask` (bitmask → `HashSet<ushort>` node IDs).
7172

7273
### Command Classes Layer (`src/ZWave.CommandClasses/`)
7374

@@ -82,7 +83,7 @@ Implements Z-Wave Command Classes (Z-Wave Application Specification). This proje
8283

8384
- **`Driver`** — Entry point. Implements `IDriver`. Opens serial port, manages frame send/receive, processes unsolicited requests, coordinates request-response and callback flows.
8485
- **`Controller`** — Represents the Z-Wave USB controller. Runs identification sequence on startup.
85-
- **`Node`** — Represents a Z-Wave network node. Implements `INode`. Handles interviews and command class discovery.
86+
- **`Node`** — Represents a Z-Wave network node. Implements `INode`. Handles interviews and command class discovery. Node IDs are `ushort` throughout the codebase to support both classic (1–232) and Long Range (256+) nodes.
8687

8788
### Source Generators (`src/ZWave.BuildTools/`)
8889

@@ -108,11 +109,12 @@ Uses `Microsoft.Extensions.Logging` with source-generated `[LoggerMessage]` attr
108109
- Tests use **MSTest** with the `MSTest.Sdk` project SDK and the **Microsoft.Testing.Platform** runner.
109110
- Tests run in parallel at method level.
110111
- Serial API command tests inherit from `CommandTestBase` and use `TestSendableCommand` / `TestReceivableCommand` helper methods that verify frame structure round-tripping.
112+
- `CommandDataParsingHelpersTests` covers the shared parsing helpers (`ParseCommandClasses`, `ParseNodeBitmask`).
111113
- Test files mirror the source structure: `src/ZWave.Serial.Tests/Commands/` contains tests for each serial command.
112114

113115
## Adding New Functionality
114116

115-
**New Serial API Command**: Create a struct in `src/ZWave.Serial/Commands/` implementing `ICommand<T>` (and `IRequestWithCallback<T>` if it uses callbacks). Add the command ID to `CommandId` enum. Add tests in `src/ZWave.Serial.Tests/Commands/`.
117+
**New Serial API Command**: Create a struct in `src/ZWave.Serial/Commands/` implementing `ICommand<T>` (and `IRequestWithCallback<T>` if it uses callbacks). Add the command ID to `CommandId` enum. Add tests in `src/ZWave.Serial.Tests/Commands/`. Node ID parameters are `ushort`; cast to `(byte)nodeId` when writing to the buffer. Use `CommandDataParsingHelpers` for shared parsing (node bitmasks, command class lists).
116118

117119
**New Command Class**: Create a class in `src/ZWave.CommandClasses/` inheriting `CommandClass<TEnum>`. Apply `[CommandClass(CommandClassId.X)]`. Define private inner structs for each command (Set/Get/Report) implementing `ICommand`. The source generator auto-registers it.
118120

‎docs/architecture.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ Each Z-Wave Serial API function is represented as a struct. These map to the fun
9999

100100
**`CommandId` enum** - All Serial API function IDs (e.g. `SendData = 0x13`, `GetInitData = 0x02`).
101101

102+
**`CommandDataParsingHelpers`** (`Commands/CommandDataParsingHelpers.cs`) - Internal shared utilities for parsing common serial API response patterns:
103+
- `ParseCommandClasses` — Parses a CC byte list with the support/control mark into `CommandClassInfo` records.
104+
- `ParseNodeBitmask` — Parses a node ID bitmask (each bit = one node) into a `HashSet<ushort>`. Takes a `baseNodeId` parameter (1 for classic nodes, 256+ for LR nodes).
105+
102106
**Communication patterns:**
103107
- **Fire-and-forget:** Host sends a REQ with no response expected (e.g. `SoftReset`, `SendDataAbort`, `WatchdogKick`)
104108
- **Request → Response:** Host sends a REQ, chip replies with a RES (e.g. `MemoryGetId`, `GetLibraryVersion`, `GetRoutingInfo`, `IsFailedNode`)
@@ -107,6 +111,8 @@ Each Z-Wave Serial API function is represented as a struct. These map to the fun
107111
- **Set only:** Host sends a REQ to configure the chip with no response (e.g. `ApplicationNodeInformation`, `SetPromiscuousMode`)
108112
- **Unsolicited request:** Chip sends a REQ without the host asking (e.g. `ApplicationCommandHandler`, `ApplicationUpdate`, `ApplicationCommandHandlerBridge`, `SerialApiStarted`)
109113

114+
**Node ID encoding:** All node ID parameters in the C# API are `ushort` to support both classic (1–232) and Long Range (256+) nodes. On the wire, node IDs are currently serialized as a single byte (8-bit mode, the default). The `Create()` methods cast `(byte)nodeId` when writing to the buffer.
115+
110116
### Command Classes (`src/ZWave.CommandClasses/`)
111117

112118
Implements Z-Wave Command Classes per the Z-Wave Application Specification. These are the application-level messages exchanged between Z-Wave nodes. This project references `ZWave.Protocol` but **not** `ZWave.Serial`, enabling mock driver implementations without a serial dependency.
@@ -144,6 +150,7 @@ Implements Z-Wave Command Classes per the Z-Wave Application Specification. Thes
144150
- `IdentifyAsync()` queries the chip: home/node ID, serial API capabilities, library version, controller capabilities, supported setup subcommands, SUC node ID, and init data (node list)
145151
- Promotes itself to SUC/SIS if none exists on the network
146152
- Creates `Node` instances for every node in the network
153+
- Stores nodes in a `Dictionary<ushort, Node>` keyed by node ID
147154

148155
**`Node`** (`Node.cs`) - Represents a Z-Wave network node. Implements `INode`.
149156
- `InterviewAsync()` queries protocol info, requests node info, discovers command classes, then interviews each CC in topological dependency order

‎src/ZWave.CommandClasses/IDriver.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ public interface IDriver
1010
/// </summary>
1111
Task SendCommandAsync<TCommand>(
1212
TCommand command,
13-
byte nodeId,
13+
ushort nodeId,
1414
CancellationToken cancellationToken)
1515
where TCommand : struct, ICommand;
1616

1717
/// <summary>
1818
/// Gets a node by its ID, or null if the node is not found.
1919
/// </summary>
20-
INode? GetNode(byte nodeId);
20+
INode? GetNode(ushort nodeId);
2121
}

‎src/ZWave.CommandClasses/INode.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public interface INode
88
/// <summary>
99
/// Gets the node ID.
1010
/// </summary>
11-
byte Id { get; }
11+
ushort Id { get; }
1212

1313
/// <summary>
1414
/// Gets the frequent listening mode of the node.

‎src/ZWave.CommandClasses/PowerlevelCommandClass.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public readonly record struct PowerlevelTestResult(
104104
/// <summary>
105105
/// The node ID of the node which is or has been under test.
106106
/// </summary>
107-
byte NodeId,
107+
ushort NodeId,
108108

109109
/// <summary>
110110
/// The result of the test
@@ -178,7 +178,7 @@ public async Task<PowerlevelReport> GetAsync(CancellationToken cancellationToken
178178
/// power level specified.
179179
/// </summary>
180180
public async Task<PowerlevelTestResult> TestNodeAsync(
181-
byte testNodeId,
181+
ushort testNodeId,
182182
Powerlevel powerlevel,
183183
ushort testFrameCount,
184184
CancellationToken cancellationToken)
@@ -334,12 +334,12 @@ public PowerlevelTestNodeSetCommand(CommandClassFrame frame)
334334
public CommandClassFrame Frame { get; }
335335

336336
public static PowerlevelSetCommand Create(
337-
byte testNodeId,
337+
ushort testNodeId,
338338
Powerlevel powerlevel,
339339
ushort testFrameCount)
340340
{
341341
Span<byte> commandParameters = stackalloc byte[4];
342-
commandParameters[0] = testNodeId;
342+
commandParameters[0] = (byte)testNodeId;
343343
commandParameters[1] = (byte)powerlevel;
344344
testFrameCount.WriteBytesBE(commandParameters[2..4]);
345345

@@ -389,7 +389,7 @@ public PowerlevelTestNodeReportCommand(CommandClassFrame frame)
389389
throw new ZWaveException(ZWaveErrorCode.InvalidPayload, "Powerlevel Test Node Report frame is too short");
390390
}
391391

392-
byte nodeId = frame.CommandParameters.Span[0];
392+
ushort nodeId = frame.CommandParameters.Span[0];
393393
if (nodeId == 0)
394394
{
395395
return null;

‎src/ZWave.CommandClasses/WakeUpCommandClass.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public async Task<WakeUpInterval> GetIntervalAsync(CancellationToken cancellatio
125125
/// <summary>
126126
/// Configure the Wake Up interval and destination of a node.
127127
/// </summary>
128-
public async Task SetIntervalAsync(uint wakeupIntervalInSeconds, byte wakeupDestinationNodeId, CancellationToken cancellationToken)
128+
public async Task SetIntervalAsync(uint wakeupIntervalInSeconds, ushort wakeupDestinationNodeId, CancellationToken cancellationToken)
129129
{
130130
var command = WakeUpIntervalSetCommand.Create(wakeupIntervalInSeconds, wakeupDestinationNodeId);
131131
await SendCommandAsync(command, cancellationToken).ConfigureAwait(false);
@@ -196,7 +196,7 @@ public WakeUpIntervalSetCommand(CommandClassFrame frame)
196196

197197
public CommandClassFrame Frame { get; }
198198

199-
public static WakeUpIntervalSetCommand Create(uint wakeupIntervalInSeconds, byte wakeupDestinationNodeId)
199+
public static WakeUpIntervalSetCommand Create(uint wakeupIntervalInSeconds, ushort wakeupDestinationNodeId)
200200
{
201201
Span<byte> commandParameters = stackalloc byte[4];
202202

@@ -212,7 +212,7 @@ public static WakeUpIntervalSetCommand Create(uint wakeupIntervalInSeconds, byte
212212
wakeupIntervalInSeconds.WriteBytesBE(secondsBytes);
213213
secondsBytes[1..].CopyTo(commandParameters);
214214

215-
commandParameters[3] = wakeupDestinationNodeId;
215+
commandParameters[3] = (byte)wakeupDestinationNodeId;
216216

217217
CommandClassFrame frame = CommandClassFrame.Create(CommandClassId, CommandId, commandParameters);
218218
return new WakeUpIntervalSetCommand(frame);

‎src/ZWave.Serial.Tests/Commands/AddNodeToNetworkTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class AddNodeToNetworkTests : CommandTestBase
99
private record AddNodeToNetworkCallbackData(
1010
byte SessionId,
1111
AddNodeStatus Status,
12-
byte AssignedNodeId,
12+
ushort AssignedNodeId,
1313
byte BasicDeviceClass,
1414
byte GenericDeviceClass,
1515
byte SpecificDeviceClass,

‎src/ZWave.Serial.Tests/Commands/ApplicationCommandHandlerBridgeTests.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ public class ApplicationCommandHandlerBridgeTests : CommandTestBase
88
{
99
private record ApplicationCommandHandlerBridgeData(
1010
ReceivedStatus ReceivedStatus,
11-
byte DestinationNodeId,
12-
byte SourceNodeId,
11+
ushort DestinationNodeId,
12+
ushort SourceNodeId,
1313
ReadOnlyMemory<byte> Payload,
1414
RssiMeasurement ReceivedRssi);
1515

‎src/ZWave.Serial.Tests/Commands/ApplicationCommandHandlerTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class ApplicationCommandHandlerTests : CommandTestBase
88
{
99
private record ApplicationCommandHandlerData(
1010
ReceivedStatus ReceivedStatus,
11-
byte NodeId,
11+
ushort NodeId,
1212
ReadOnlyMemory<byte> Payload,
1313
RssiMeasurement ReceivedRssi);
1414

‎src/ZWave.Serial.Tests/Commands/ApplicationUpdateTests.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ namespace ZWave.Serial.Tests.Commands;
77
public class ApplicationUpdateTests : CommandTestBase
88
{
99
private record ApplicationUpdateGenericData(
10-
byte NodeId,
10+
ushort NodeId,
1111
byte BasicDeviceClass,
1212
byte GenericDeviceClass,
1313
byte SpecificDeviceClass,
1414
IReadOnlyList<CommandClassInfo> CommandClasses);
1515

1616
private record ApplicationUpdateSmartStartPrimeData(
17-
byte NodeId,
17+
ushort NodeId,
1818
ReceivedStatus ReceivedStatus,
1919
uint HomeId,
2020
byte BasicDeviceClass,
@@ -23,7 +23,7 @@ private record ApplicationUpdateSmartStartPrimeData(
2323
IReadOnlyList<CommandClassInfo> CommandClasses);
2424

2525
private record ApplicationUpdateSmartStartIncludedNodeInfoData(
26-
byte NodeId,
26+
ushort NodeId,
2727
ReceivedStatus ReceivedStatus,
2828
uint HomeId);
2929

0 commit comments

Comments
 (0)