Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Z-Wave Long Range #6401

Merged
merged 45 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
666257e
feat(workaround): zst39 workaround for corrupted soft reset ack
jtbraun Oct 15, 2023
acfb9ea
feat(longrange): add FunctionType values
jtbraun Oct 13, 2023
5f81ca9
fix(functype): add comment about source of FunctionType=0x28
jtbraun Oct 16, 2023
b4ddd89
feat(longrange): support encoding/parsing node information
jtbraun Oct 14, 2023
5eb0762
feat(longrange): handle node ids > 256 in S2 auth data
jtbraun Oct 16, 2023
fcaacb3
feat(longrange): add long range command class
jtbraun Oct 14, 2023
29ef9af
feat(squash): with other ZWaveLRProtocolCC.ts changes
jtbraun Oct 16, 2023
1b56690
feat(squash): with other index.ts changes
jtbraun Oct 16, 2023
63d142b
feat(squash): with other Constants.ts
jtbraun Oct 16, 2023
6005dfa
feat(squash): with other Protocols.ts
jtbraun Oct 16, 2023
cd27d02
feat(longrange): the bulk of long range support
jtbraun Oct 16, 2023
8f15e3a
fix(lint): lint errors
jtbraun Oct 16, 2023
d1e23fc
fix(lint): lint errors
jtbraun Oct 16, 2023
70038ab
Merge remote-tracking branch 'upstream/master' into feature/longrange
jtbraun Nov 15, 2023
9b679c3
feat(longrange): remove corrupted ack workaround
jtbraun Nov 15, 2023
9637127
feat(longrange): remove ZWaveLRProtocolCC
jtbraun Nov 15, 2023
8223ee9
feat(longrange): remove "Protocol" from the LR CC name
jtbraun Nov 15, 2023
e8f9023
feat(longrange): move NodeInfo parsing/encoding changes one function …
jtbraun Nov 15, 2023
02052ef
feat(longrange): expand LR to LongRange in smart start messages
jtbraun Nov 15, 2023
c9184ef
feat(longrange): remove comment about long range CC
jtbraun Nov 15, 2023
d00b084
feat(longrange): undo changes to GetSerialApiCapabilitiesRequest
jtbraun Nov 15, 2023
15bc808
feat(longrange): use segment for GetLongRangeNodes*
jtbraun Nov 15, 2023
7b268b4
feat(longrange): array.push, not array.concat to append
jtbraun Nov 15, 2023
1bb54b2
feat(longrange): move fetching LR nodes to a helper method
jtbraun Nov 15, 2023
9b5c7ba
feat(longrange): move LongRangeChannel to core..Protocols.ts
jtbraun Nov 15, 2023
32c0c34
feat(longrange): remove validate length comments
jtbraun Nov 15, 2023
25908de
feat(longrange): remove ResponseStatus
jtbraun Nov 15, 2023
e8c3134
feat(longrange): fix erronous boolean instead of bitwise and
jtbraun Nov 15, 2023
6e52505
feat(longrange): fix lint errors
jtbraun Nov 15, 2023
e55e479
feat(longrange): lint fixes
jtbraun Nov 15, 2023
f0d0dde
feat(longrange): change LR=>LongRange, condition some serial api setu…
jtbraun Nov 15, 2023
2e7971d
fix: only pass Z-Wave LR protocol flag to SmartStart inclusion
AlCalzone Jan 16, 2024
8715fce
refactor: cleanup, prevent managing routes for LR, highlight some FIXMEs
AlCalzone Jan 16, 2024
f9585f0
chore: more cleanup
AlCalzone Jan 16, 2024
fe446eb
fix: default to the first supported protocol
AlCalzone Jan 17, 2024
79b1a6a
chore: rework controller info message
AlCalzone Jan 17, 2024
8f22fd1
refactor: rework initialization sequence
AlCalzone Jan 17, 2024
a42243d
fix: restore original encoding of GetSerialApiInitDataResponse
AlCalzone Jan 17, 2024
683db90
fix: typo
AlCalzone Jan 17, 2024
ea170ce
fix: split GetLongRangeNodes into own file, fix query loop
AlCalzone Jan 17, 2024
f226a82
fix: node info parsing
AlCalzone Jan 17, 2024
1e09888
fix: remember node ID when requesting protocol info
AlCalzone Jan 18, 2024
098b8d1
fix: cleanup
AlCalzone Jan 19, 2024
c58c2fa
docs: add documentation
AlCalzone Jan 19, 2024
01039de
Merge branch 'master' into pr/jtbraun/6401-1
AlCalzone Jan 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions packages/cc/src/cc/Security2CC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,26 @@ function getAuthenticationData(
commandLength: number,
unencryptedPayload: Buffer,
): Buffer {
const ret = Buffer.allocUnsafe(8 + unencryptedPayload.length);
ret[0] = sendingNodeId;
ret[1] = destination;
ret.writeUInt32BE(homeId, 2);
ret.writeUInt16BE(commandLength, 6);
const nodeIdSize = (sendingNodeId < 256 && destination < 256) ? 1 : 2;
const ret = Buffer.allocUnsafe(2*nodeIdSize + 6 + unencryptedPayload.length);
let offset = 0;
if (nodeIdSize == 1) {
ret[offset++] = sendingNodeId;
ret[offset++] = destination;

} else {
ret.writeUint16BE(sendingNodeId, offset);
offset += 2;
ret.writeUint16BE(destination, offset);
offset += 2;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (nodeIdSize == 1) {
ret[offset++] = sendingNodeId;
ret[offset++] = destination;
} else {
ret.writeUint16BE(sendingNodeId, offset);
offset += 2;
ret.writeUint16BE(destination, offset);
offset += 2;
}
ret.writeUIntBE(sendingNodeId, offset, nodeIdSize);
offset += nodeIdSize;
ret.writeUIntBE(destination, offset, nodeIdSize);
offset += nodeIdSize;


ret.writeUInt32BE(homeId, offset);
offset += 4;
ret.writeUInt16BE(commandLength, offset);
offset += 2;
// This includes the sequence number and all unencrypted extensions
unencryptedPayload.copy(ret, 8, 0);
unencryptedPayload.copy(ret, offset, 0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If either nodeid is 256+, you have to use uint16BE to encode both.

return ret;
}

Expand Down
234 changes: 234 additions & 0 deletions packages/cc/src/cc/ZWaveLRProtocolCC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this command class is defined in the specs, It's not exposed that I saw in any command class list, so I'm not sure where/how this would be used. Maybe if you had a secondary LR controller in the network? Unknown. We may not need to include this at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be used in our testing framework, but unless you're sure about these things, I'd leave it out for now.

CommandClasses,
type DataRate,
type FLiRS,
type NodeInformationFrame,
type NodeType,
type ProtocolVersion,
ZWaveError,
ZWaveErrorCodes,
encodeNodeInformationFrame,
parseNodeInformationFrame,
validatePayload,
} from "@zwave-js/core";
import type { ZWaveHost } from "@zwave-js/host";
import {
type CCCommandOptions,
CommandClass,
type CommandClassDeserializationOptions,
gotDeserializationOptions,
} from "../lib/CommandClass";
import {
CCCommand,
commandClass,
expectedCCResponse,
implementedVersion,
} from "../lib/CommandClassDecorators";
import {
ZWaveLRProtocolCommand,
} from "../lib/_Types";

@commandClass(CommandClasses["Z-Wave Long Range Protocol"])
@implementedVersion(1)
export class ZWaveLRProtocolCC extends CommandClass {
declare ccCommand: ZWaveLRProtocolCommand;
}

@CCCommand(ZWaveLRProtocolCommand.NOP)
export class ZWaveLRProtocolCCNOP extends ZWaveLRProtocolCC {}

interface ZWaveLRProtocolCCNodeInformationFrameOptions
extends CCCommandOptions, NodeInformationFrame
{}

// BUGBUG: how much of this can we share with existing stuff? Can we use a ZWaveProtocolCCNodeInformationFrameOptions field to do the `isLongRange` stuff?
// BUGBUG: how much can we share also with the Smart Start things below that are VERY close to this stuff?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, can we leverage base classes and combine a lot of this with the existing ZWaveProtocolCC?

@CCCommand(ZWaveLRProtocolCommand.NodeInformationFrame)
export class ZWaveLRProtocolCCNodeInformationFrame extends ZWaveLRProtocolCC
implements NodeInformationFrame
{
public constructor(
host: ZWaveHost,
options:
| CommandClassDeserializationOptions
| ZWaveLRProtocolCCNodeInformationFrameOptions,
) {
super(host, options);

let nif: NodeInformationFrame;
if (gotDeserializationOptions(options)) {
nif = parseNodeInformationFrame(this.payload, true);
} else {
nif = options;
}

this.basicDeviceClass = 0x100; // BUGBUG: what fake value can we safely use here?
this.genericDeviceClass = nif.genericDeviceClass;
this.specificDeviceClass = nif.specificDeviceClass;
this.isListening = nif.isListening;
this.isFrequentListening = nif.isFrequentListening;
this.isRouting = false;
this.supportedDataRates = nif.supportedDataRates;
this.protocolVersion = 0; // "unknown";
this.optionalFunctionality = false;
this.nodeType = nif.nodeType;
this.supportsSecurity = nif.supportsSecurity;
this.supportsBeaming = false;
this.supportedCCs = nif.supportedCCs;
}

public basicDeviceClass: number;
public genericDeviceClass: number;
public specificDeviceClass: number;
public isListening: boolean;
public isFrequentListening: FLiRS;
public isRouting: boolean;
public supportedDataRates: DataRate[];
public protocolVersion: ProtocolVersion;
public optionalFunctionality: boolean;
public nodeType: NodeType;
public supportsSecurity: boolean;
public supportsBeaming: boolean;
public supportedCCs: CommandClasses[];

public serialize(): Buffer {
this.payload = encodeNodeInformationFrame(this, true);
return super.serialize();
}
}

@CCCommand(ZWaveLRProtocolCommand.RequestNodeInformationFrame)
@expectedCCResponse(ZWaveLRProtocolCCNodeInformationFrame)
export class ZWaveLRProtocolCCRequestNodeInformationFrame
extends ZWaveLRProtocolCC
{}

interface ZWaveLRProtocolCCAssignIDsOptions extends CCCommandOptions {
assignedNodeId: number;
homeId: number;
}

@CCCommand(ZWaveLRProtocolCommand.AssignIDs)
export class ZWaveLRProtocolCCAssignIDs extends ZWaveLRProtocolCC {
public constructor(
host: ZWaveHost,
options:
| CommandClassDeserializationOptions
| ZWaveLRProtocolCCAssignIDsOptions,
) {
super(host, options);
if (gotDeserializationOptions(options)) {
validatePayload(this.payload.length >= 6);
this.assignedNodeId = this.payload.readUInt16BE(0) & 0xFFF;
this.homeId = this.payload.readUInt32BE(2);
} else {
this.assignedNodeId = options.assignedNodeId;
this.homeId = options.homeId;
}
}

public assignedNodeId: number;
public homeId: number;

public serialize(): Buffer {
this.payload = Buffer.allocUnsafe(6);
this.payload.writeUInt16BE(this.assignedNodeId, 0);
this.payload.writeUInt32BE(this.homeId, 2);
return super.serialize();
}
}

@CCCommand(ZWaveLRProtocolCommand.ExcludeRequest)
export class ZWaveLRProtocolCCExcludeRequest
extends ZWaveLRProtocolCCNodeInformationFrame
{}

interface ZWaveLRProtocolCCSmartStartIncludedNodeInformationOptions
extends CCCommandOptions
{
nwiHomeId: Buffer;
}

// BUGBUG: this is exactly equal to the ZWaveProtocolCommand.SmartStartIncludedNodeInformation, can we reuse/inherit that somehow?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As comment, how much sharing do we want to do between these classes (or not)?

@CCCommand(ZWaveLRProtocolCommand.SmartStartIncludedNodeInformation)
export class ZWaveLRProtocolCCSmartStartIncludedNodeInformation
extends ZWaveLRProtocolCC
{
public constructor(
host: ZWaveHost,
options:
| CommandClassDeserializationOptions
| ZWaveLRProtocolCCSmartStartIncludedNodeInformationOptions,
) {
super(host, options);
if (gotDeserializationOptions(options)) {
validatePayload(this.payload.length >= 4);
this.nwiHomeId = this.payload.subarray(0, 4);
} else {
if (options.nwiHomeId.length !== 4) {
throw new ZWaveError(
`nwiHomeId must have length 4`,
ZWaveErrorCodes.Argument_Invalid,
);
}
this.nwiHomeId = options.nwiHomeId;
}
}

public nwiHomeId: Buffer;

public serialize(): Buffer {
this.payload = Buffer.from(this.nwiHomeId);
return super.serialize();
}
}

// BUGBUG this needs to include support for Sensor256ms and BeamCapability fields, yet the GetNodeInfo reserves those
@CCCommand(ZWaveLRProtocolCommand.SmartStartPrime)
export class ZWaveLRProtocolCCSmartStartPrime
extends ZWaveLRProtocolCCNodeInformationFrame
{}

// BUGBUG this needs to include support for Sensor256ms and BeamCapability fields, yet the GetNodeInfo reserves those
@CCCommand(ZWaveLRProtocolCommand.SmartStartInclusionRequest)
export class ZWaveLRProtocolCCSmartStartInclusionRequest
extends ZWaveLRProtocolCCNodeInformationFrame
{}

// BUGBUG: this is identical to the AssignNodeID message, except for the field names
@CCCommand(ZWaveLRProtocolCommand.ExcludeRequestConfirimation)
export class ZWaveLRProtocolCCExcludeRequestConfirimation
extends ZWaveLRProtocolCC
{
public constructor(
host: ZWaveHost,
options:
| CommandClassDeserializationOptions
| ZWaveLRProtocolCCAssignIDsOptions,
) {
super(host, options);
if (gotDeserializationOptions(options)) {
validatePayload(this.payload.length >= 6);
this.requestingNodeId = this.payload.readUInt16BE(0) & 0xFFF;
this.homeId = this.payload.readUInt32BE(2);
} else {
this.requestingNodeId = options.assignedNodeId;
this.homeId = options.homeId;
}
}

public requestingNodeId: number;
public homeId: number;

public serialize(): Buffer {
this.payload = Buffer.allocUnsafe(6);
this.payload.writeUInt16BE(this.requestingNodeId, 0);
this.payload.writeUInt32BE(this.homeId, 2);
return super.serialize();
}
}

@CCCommand(ZWaveLRProtocolCommand.NonSecureIncusionStepComplete)
export class ZWaveLRProtocolCCNonSecureIncusionStepComplete
extends ZWaveLRProtocolCC
{}
13 changes: 13 additions & 0 deletions packages/cc/src/cc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,19 @@ export {
WindowCoveringCCSupportedReport,
WindowCoveringCCValues,
} from "./WindowCoveringCC";
export {
ZWaveLRProtocolCC,
ZWaveLRProtocolCCAssignIDs,
ZWaveLRProtocolCCExcludeRequest,
ZWaveLRProtocolCCExcludeRequestConfirimation,
ZWaveLRProtocolCCNOP,
ZWaveLRProtocolCCNodeInformationFrame,
ZWaveLRProtocolCCNonSecureIncusionStepComplete,
ZWaveLRProtocolCCRequestNodeInformationFrame,
ZWaveLRProtocolCCSmartStartIncludedNodeInformation,
ZWaveLRProtocolCCSmartStartInclusionRequest,
ZWaveLRProtocolCCSmartStartPrime,
} from "./ZWaveLRProtocolCC";
export {
ZWavePlusCC,
ZWavePlusCCGet,
Expand Down
16 changes: 16 additions & 0 deletions packages/cc/src/lib/_Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,22 @@ export enum ZWaveProtocolCommand {
SmartStartInclusionRequest = 0x28,
}

export enum ZWaveLRProtocolCommand {
NOP = 0x00,

// BUGBUG: all defined above, can they be shared?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have the same # assignments, but are in a separate namespace. Add the missing ones to the list above, or leave these separate (or maybe just reference Foo = ZWaveProtocolCommand.Foo.value or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave separate IMO, or leave it out completely together with the CC

NodeInformationFrame = 0x01,
RequestNodeInformationFrame = 0x02,
AssignIDs = 0x03,
ExcludeRequest = 0x23,
SmartStartIncludedNodeInformation = 0x26,
SmartStartPrime = 0x27,
SmartStartInclusionRequest = 0x28,

ExcludeRequestConfirimation = 0x29,
NonSecureIncusionStepComplete = 0x2A,
}

export enum WakeUpTime {
None,
"1000ms",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/capabilities/CommandClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export enum CommandClasses {
"Z-Wave Plus Info" = 0x5e,
// Internal CC which is not used directly by applications
"Z-Wave Protocol" = 0x01,
"Z-Wave Long Range Protocol" = 0x04,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any preference on this string?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specs call it "Z-Wave Long Range" Command Class, let's stick to that.

}

export function getCCName(cc: number): string {
Expand Down
Loading