-
-
Notifications
You must be signed in to change notification settings - Fork 630
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
Changes from 13 commits
666257e
acfb9ea
5f81ca9
b4ddd89
5eb0762
fcaacb3
29ef9af
1b56690
63d142b
6005dfa
cd27d02
8f15e3a
d1e23fc
70038ab
9b679c3
9637127
8223ee9
e8f9023
02052ef
c9184ef
d00b084
15bc808
7b268b4
1bb54b2
9b5c7ba
32c0c34
25908de
e8c3134
6e52505
e55e479
f0d0dde
2e7971d
8715fce
f9585f0
fe446eb
79b1a6a
8f22fd1
a42243d
683db90
ea170ce
f226a82
1e09888
098b8d1
c58c2fa
01039de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
import { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
@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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1670,6 +1670,22 @@ export enum ZWaveProtocolCommand { | |
SmartStartInclusionRequest = 0x28, | ||
} | ||
|
||
export enum ZWaveLRProtocolCommand { | ||
NOP = 0x00, | ||
|
||
// BUGBUG: all defined above, can they be shared? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any preference on this string? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The specs call it |
||
} | ||
|
||
export function getCCName(cc: number): string { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.