diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets index a6d55319776e..5799ddeb1af3 100644 --- a/.vscode/typescript.code-snippets +++ b/.vscode/typescript.code-snippets @@ -664,7 +664,7 @@ "scope": "typescript", "prefix": "zwccpv", "body": [ - "public persistValues(applHost: ZWaveApplicationHost): boolean {", + "public persistValues(applHost: ZWaveApplicationHost): boolean {", "\tif (!super.persistValues(applHost)) return false;", "\tconst valueDB = this.getValueDB(applHost);", "", @@ -682,6 +682,7 @@ "\tCommandClass,", "\tgotDeserializationOptions,", "\ttype CCCommandOptions,", + "\ttype CCNode,", "\ttype CommandClassDeserializationOptions,", "} from \"../lib/CommandClass\";", "import {", @@ -951,7 +952,7 @@ "scope": "typescript", "prefix": "zwccinterview", "body": [ - "public async interview(applHost: ZWaveApplicationHost): Promise {", + "public async interview(applHost: ZWaveApplicationHost): Promise {", "\tconst node = this.getNode(applHost)!;", "\tconst endpoint = this.getEndpoint(applHost)!;", "\tconst api = CCAPI.create(", @@ -1003,7 +1004,7 @@ "scope": "typescript", "prefix": "zwccrefval", "body": [ - "public async refreshValues(applHost: ZWaveApplicationHost): Promise {", + "public async refreshValues(applHost: ZWaveApplicationHost): Promise {", "\tconst node = this.getNode(applHost)!;", "\tconst endpoint = this.getEndpoint(applHost)!;", "\tconst api = CCAPI.create(", diff --git a/docs/api/endpoint.md b/docs/api/endpoint.md index 27876ce8d137..100479635005 100644 --- a/docs/api/endpoint.md +++ b/docs/api/endpoint.md @@ -56,10 +56,10 @@ createCCInstanceUnsafe(cc: CommandClasses): T | undefined Like [`createCCInstance`](#createCCInstance) but returns `undefined` instead of throwing when a CC is not supported. -### `getNodeUnsafe` +### `tryGetNode` ```ts -getNodeUnsafe(): ZWaveNode | undefined +tryGetNode(): ZWaveNode | undefined ``` Returns the node this endpoint belongs to (or undefined if the node doesn't exist). diff --git a/packages/cc/src/cc/AlarmSensorCC.ts b/packages/cc/src/cc/AlarmSensorCC.ts index 5e4fcf8f4ea8..3962536b628d 100644 --- a/packages/cc/src/cc/AlarmSensorCC.ts +++ b/packages/cc/src/cc/AlarmSensorCC.ts @@ -1,6 +1,6 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -11,11 +11,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, isEnumMember, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -23,6 +19,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -129,12 +128,12 @@ export class AlarmSensorCCAPI extends PhysicalCCAPI { public async get(sensorType?: AlarmSensorType) { this.assertSupportsCommand(AlarmSensorCommand, AlarmSensorCommand.Get); - const cc = new AlarmSensorCCGet(this.applHost, { + const cc = new AlarmSensorCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sensorType, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -148,11 +147,11 @@ export class AlarmSensorCCAPI extends PhysicalCCAPI { AlarmSensorCommand.SupportedGet, ); - const cc = new AlarmSensorCCSupportedGet(this.applHost, { + const cc = new AlarmSensorCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AlarmSensorCCSupportedReport >( cc, @@ -168,38 +167,40 @@ export class AlarmSensorCCAPI extends PhysicalCCAPI { export class AlarmSensorCC extends CommandClass { declare ccCommand: AlarmSensorCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; // Skip the interview in favor of Notification CC if possible if (endpoint.supportsCC(CommandClasses.Notification)) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `${this.constructor.name}: skipping interview because Notification CC is supported...`, direction: "none", }); - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); return; } const api = CCAPI.create( CommandClasses["Alarm Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Find out which sensor types this sensor supports - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported sensor types...", direction: "outbound", @@ -212,13 +213,13 @@ export class AlarmSensorCC extends CommandClass { .map((name) => `\n· ${name}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported sensor types timed out, skipping interview...", @@ -228,25 +229,27 @@ export class AlarmSensorCC extends CommandClass { } // Query (all of) the sensor's current value(s) - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Alarm Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const supportedSensorTypes: readonly AlarmSensorType[] = - this.getValue(applHost, AlarmSensorCCValues.supportedSensorTypes) + this.getValue(ctx, AlarmSensorCCValues.supportedSensorTypes) ?? []; // Always query (all of) the sensor's current value(s) @@ -257,7 +260,7 @@ export class AlarmSensorCC extends CommandClass { const sensorName = getEnumMemberName(AlarmSensorType, type); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current value for ${sensorName}...`, direction: "outbound", @@ -274,7 +277,7 @@ severity: ${currentValue.severity}`; message += ` duration: ${currentValue.duration}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message, direction: "inbound", @@ -288,10 +291,10 @@ duration: ${currentValue.duration}`; * This only works AFTER the interview process */ public static getSupportedSensorTypesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( AlarmSensorCCValues.supportedSensorTypes.endpoint( @@ -301,7 +304,7 @@ duration: ${currentValue.duration}`; } protected createMetadataForSensorType( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, sensorType: AlarmSensorType, ): void { const stateValue = AlarmSensorCCValues.state(sensorType); @@ -309,19 +312,18 @@ duration: ${currentValue.duration}`; const durationValue = AlarmSensorCCValues.duration(sensorType); // Always create metadata if it does not exist - this.ensureMetadata(applHost, stateValue); - this.ensureMetadata(applHost, severityValue); - this.ensureMetadata(applHost, durationValue); + this.ensureMetadata(ctx, stateValue); + this.ensureMetadata(ctx, severityValue); + this.ensureMetadata(ctx, durationValue); } } @CCCommand(AlarmSensorCommand.Report) export class AlarmSensorCCReport extends AlarmSensorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 5, this.payload[1] !== 0xff); // Alarm Sensor reports may be forwarded by a different node, in this case // (and only then!) the payload contains the original node ID @@ -345,7 +347,7 @@ export class AlarmSensorCCReport extends AlarmSensorCC { public readonly severity: number | undefined; public readonly duration: number | undefined; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "sensor type": getEnumMemberName(AlarmSensorType, this.sensorType), "alarm state": this.state, @@ -357,23 +359,23 @@ export class AlarmSensorCCReport extends AlarmSensorCC { message.duration = `${this.duration} seconds`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Create metadata if it does not exist - this.createMetadataForSensorType(applHost, this.sensorType); + this.createMetadataForSensorType(ctx, this.sensorType); const stateValue = AlarmSensorCCValues.state(this.sensorType); const severityValue = AlarmSensorCCValues.severity(this.sensorType); const durationValue = AlarmSensorCCValues.duration(this.sensorType); - this.setValue(applHost, stateValue, this.state); - this.setValue(applHost, severityValue, this.severity); - this.setValue(applHost, durationValue, this.duration); + this.setValue(ctx, stateValue, this.state); + this.setValue(ctx, severityValue, this.severity); + this.setValue(ctx, durationValue, this.duration); return true; } @@ -399,10 +401,9 @@ export interface AlarmSensorCCGetOptions extends CCCommandOptions { @expectedCCResponse(AlarmSensorCCReport, testResponseForAlarmSensorGet) export class AlarmSensorCCGet extends AlarmSensorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | AlarmSensorCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -416,14 +417,14 @@ export class AlarmSensorCCGet extends AlarmSensorCC { public sensorType: AlarmSensorType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sensorType]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "sensor type": getEnumMemberName( AlarmSensorType, @@ -437,10 +438,9 @@ export class AlarmSensorCCGet extends AlarmSensorCC { @CCCommand(AlarmSensorCommand.SupportedReport) export class AlarmSensorCCSupportedReport extends AlarmSensorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); const bitMaskLength = this.payload[0]; validatePayload(this.payload.length >= 1 + bitMaskLength); @@ -456,18 +456,18 @@ export class AlarmSensorCCSupportedReport extends AlarmSensorCC { return this._supportedSensorTypes; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Create metadata for each sensor type for (const type of this._supportedSensorTypes) { - this.createMetadataForSensorType(applHost, type); + this.createMetadataForSensorType(ctx, type); } return true; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported sensor types": this._supportedSensorTypes .map((t) => getEnumMemberName(AlarmSensorType, t)) diff --git a/packages/cc/src/cc/AssociationCC.ts b/packages/cc/src/cc/AssociationCC.ts index b65b01750b44..31a9157602c6 100644 --- a/packages/cc/src/cc/AssociationCC.ts +++ b/packages/cc/src/cc/AssociationCC.ts @@ -1,5 +1,5 @@ import type { - IZWaveEndpoint, + EndpointId, MaybeNotKnown, MessageRecord, SupervisionResult, @@ -14,9 +14,10 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; @@ -25,6 +26,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -103,11 +106,11 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.SupportedGroupingsGet, ); - const cc = new AssociationCCSupportedGroupingsGet(this.applHost, { + const cc = new AssociationCCSupportedGroupingsGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationCCSupportedGroupingsReport >( cc, @@ -123,12 +126,12 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.SupportedGroupingsReport, ); - const cc = new AssociationCCSupportedGroupingsReport(this.applHost, { + const cc = new AssociationCCSupportedGroupingsReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupCount, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -139,12 +142,12 @@ export class AssociationCCAPI extends PhysicalCCAPI { public async getGroup(groupId: number) { this.assertSupportsCommand(AssociationCommand, AssociationCommand.Get); - const cc = new AssociationCCGet(this.applHost, { + const cc = new AssociationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -165,12 +168,12 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.Report, ); - const cc = new AssociationCCReport(this.applHost, { + const cc = new AssociationCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -183,13 +186,13 @@ export class AssociationCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(AssociationCommand, AssociationCommand.Set); - const cc = new AssociationCCSet(this.applHost, { + const cc = new AssociationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, nodeIds, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -204,12 +207,27 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.Remove, ); - const cc = new AssociationCCRemove(this.applHost, { + // Validate options + if (!options.groupId) { + if (this.version === 1) { + throw new ZWaveError( + `Node ${this.endpoint.nodeId} only supports AssociationCC V1 which requires the group Id to be set`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + } else if (options.groupId < 0) { + throw new ZWaveError( + "The group id must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + + const cc = new AssociationCCRemove({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -251,11 +269,11 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.SpecificGroupGet, ); - const cc = new AssociationCCSpecificGroupGet(this.applHost, { + const cc = new AssociationCCSpecificGroupGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationCCSpecificGroupReport >( cc, @@ -276,12 +294,12 @@ export class AssociationCCAPI extends PhysicalCCAPI { AssociationCommand.SpecificGroupReport, ); - const cc = new AssociationCCSpecificGroupReport(this.applHost, { + const cc = new AssociationCCSpecificGroupReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, group, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -306,16 +324,14 @@ export class AssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getGroupCountCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - AssociationCCValues.groupCount.endpoint(endpoint.index), - ) || 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + AssociationCCValues.groupCount.endpoint(endpoint.index), + ) || 0; } /** @@ -323,12 +339,12 @@ export class AssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getMaxNodesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & GetDeviceConfig, + endpoint: EndpointId, groupId: number, ): number { return ( - applHost + ctx .getValueDB(endpoint.nodeId) .getValue( AssociationCCValues.maxNodes(groupId).endpoint( @@ -337,7 +353,7 @@ export class AssociationCC extends CommandClass { ) // If the information is not available, fall back to the configuration file if possible // This can happen on some legacy devices which have "hidden" association groups - ?? applHost + ?? ctx .getDeviceConfig?.(endpoint.nodeId) ?.getAssociationConfigForEndpoint(endpoint.index, groupId) ?.maxNodes @@ -350,12 +366,12 @@ export class AssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getAllDestinationsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): ReadonlyMap { const ret = new Map(); - const groupCount = this.getGroupCountCached(applHost, endpoint); - const valueDB = applHost.getValueDB(endpoint.nodeId); + const groupCount = this.getGroupCountCached(ctx, endpoint); + const valueDB = ctx.getValueDB(endpoint.nodeId); for (let i = 1; i <= groupCount; i++) { // Add all root destinations const nodes = valueDB.getValue( @@ -371,18 +387,20 @@ export class AssociationCC extends CommandClass { return ret; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -393,20 +411,20 @@ export class AssociationCC extends CommandClass { // multi channel association groups // Find out how many groups are supported - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying number of association groups...", direction: "outbound", }); const groupCount = await api.getGroupCount(); if (groupCount != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `supports ${groupCount} association groups`, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying association groups timed out, skipping interview...", @@ -416,46 +434,48 @@ export class AssociationCC extends CommandClass { } // Query each association group for its members - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Skip the remaining Association CC interview in favor of Multi Channel Association if possible if (endpoint.supportsCC(CommandClasses["Multi Channel Association"])) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `${this.constructor.name}: delaying configuration of lifeline associations until after Multi Channel Association interview...`, direction: "none", }); - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); return; } // And set up lifeline associations - await ccUtils.configureLifelineAssociations(applHost, endpoint); + await ccUtils.configureLifelineAssociations(ctx, endpoint); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const groupCount = AssociationCC.getGroupCountCached( - applHost, + ctx, endpoint, ); // Query each association group for (let groupId = 1; groupId <= groupCount; groupId++) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying association group #${groupId}...`, direction: "outbound", @@ -466,7 +486,7 @@ export class AssociationCC extends CommandClass { `received information for association group #${groupId}: maximum # of nodes: ${group.maxNodes} currently assigned nodes: ${group.nodeIds.map(String).join(", ")}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -486,10 +506,9 @@ export interface AssociationCCSetOptions extends CCCommandOptions { @useSupervision() export class AssociationCCSet extends AssociationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | AssociationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.groupId = this.payload[0]; @@ -515,12 +534,12 @@ export class AssociationCCSet extends AssociationCC { public groupId: number; public nodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupId, ...this.nodeIds]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "group id": this.groupId || "all groups", "node ids": this.nodeIds.length @@ -528,7 +547,7 @@ export class AssociationCCSet extends AssociationCC { : "all nodes", }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -546,12 +565,11 @@ export interface AssociationCCRemoveOptions { @useSupervision() export class AssociationCCRemove extends AssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (AssociationCCRemoveOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); if (this.payload[0] !== 0) { @@ -559,22 +577,6 @@ export class AssociationCCRemove extends AssociationCC { } this.nodeIds = [...this.payload.subarray(1)]; } else { - // Validate options - if (!options.groupId) { - if (this.version === 1) { - throw new ZWaveError( - `Node ${this - .nodeId as number} only supports AssociationCC V1 which requires the group Id to be set`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - } else if (options.groupId < 0) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - // When removing associations, we allow invalid node IDs. // See GH#3606 - it is possible that those exist. this.groupId = options.groupId; @@ -585,15 +587,15 @@ export class AssociationCCRemove extends AssociationCC { public groupId?: number; public nodeIds?: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.groupId || 0, ...(this.nodeIds || []), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "group id": this.groupId || "all groups", "node ids": this.nodeIds && this.nodeIds.length @@ -601,7 +603,7 @@ export class AssociationCCRemove extends AssociationCC { : "all nodes", }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -618,12 +620,11 @@ export interface AssociationCCReportSpecificOptions { @CCCommand(AssociationCommand.Report) export class AssociationCCReport extends AssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (AssociationCCReportSpecificOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -665,8 +666,8 @@ export class AssociationCCReport extends AssociationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: AssociationCCReport[], + _ctx: CCParsingContext, ): void { // Concat the list of nodes this.nodeIds = [...partials, this] @@ -674,19 +675,19 @@ export class AssociationCCReport extends AssociationCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.groupId, this.maxNodes, this.reportsToFollow, ...this.nodeIds, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "max # of nodes": this.maxNodes, @@ -706,10 +707,9 @@ export interface AssociationCCGetOptions extends CCCommandOptions { @expectedCCResponse(AssociationCCReport) export class AssociationCCGet extends AssociationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | AssociationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.groupId = this.payload[0]; @@ -726,14 +726,14 @@ export class AssociationCCGet extends AssociationCC { public groupId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId }, }; } @@ -749,12 +749,11 @@ export interface AssociationCCSupportedGroupingsReportOptions @CCCommand(AssociationCommand.SupportedGroupingsReport) export class AssociationCCSupportedGroupingsReport extends AssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationCCSupportedGroupingsReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -767,14 +766,14 @@ export class AssociationCCSupportedGroupingsReport extends AssociationCC { @ccValue(AssociationCCValues.groupCount) public groupCount: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupCount]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group count": this.groupCount }, }; } @@ -792,12 +791,11 @@ export interface AssociationCCSpecificGroupReportOptions { @CCCommand(AssociationCommand.SpecificGroupReport) export class AssociationCCSpecificGroupReport extends AssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (AssociationCCSpecificGroupReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -809,14 +807,14 @@ export class AssociationCCSpecificGroupReport extends AssociationCC { public group: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.group]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { group: this.group }, }; } diff --git a/packages/cc/src/cc/AssociationGroupInfoCC.ts b/packages/cc/src/cc/AssociationGroupInfoCC.ts index c01890008cc9..586443444817 100644 --- a/packages/cc/src/cc/AssociationGroupInfoCC.ts +++ b/packages/cc/src/cc/AssociationGroupInfoCC.ts @@ -1,20 +1,17 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type SupportsCC, encodeCCId, getCCName, parseCCId, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { cpp2js, getEnumMemberName, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -22,6 +19,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -109,12 +109,12 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.NameGet, ); - const cc = new AssociationGroupInfoCCNameGet(this.applHost, { + const cc = new AssociationGroupInfoCCNameGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCNameReport >( cc, @@ -130,14 +130,14 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.NameReport, ); - const cc = new AssociationGroupInfoCCNameReport(this.applHost, { + const cc = new AssociationGroupInfoCCNameReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, name, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -148,13 +148,13 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.InfoGet, ); - const cc = new AssociationGroupInfoCCInfoGet(this.applHost, { + const cc = new AssociationGroupInfoCCInfoGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, refreshCache, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCInfoReport >( cc, @@ -181,13 +181,13 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.InfoReport, ); - const cc = new AssociationGroupInfoCCInfoReport(this.applHost, { + const cc = new AssociationGroupInfoCCInfoReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -202,13 +202,13 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.CommandListGet, ); - const cc = new AssociationGroupInfoCCCommandListGet(this.applHost, { + const cc = new AssociationGroupInfoCCCommandListGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, allowCache, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCCommandListReport >( cc, @@ -227,14 +227,14 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { AssociationGroupInfoCommand.CommandListReport, ); - const cc = new AssociationGroupInfoCCCommandListReport(this.applHost, { + const cc = new AssociationGroupInfoCCCommandListReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, commands, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -255,11 +255,11 @@ export class AssociationGroupInfoCC extends CommandClass { /** Returns the name of an association group */ public static getGroupNameCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, groupId: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( AssociationGroupInfoCCValues.groupName(groupId).endpoint( @@ -270,11 +270,11 @@ export class AssociationGroupInfoCC extends CommandClass { /** Returns the association profile for an association group */ public static getGroupProfileCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, groupId: number, ): MaybeNotKnown { - return applHost.getValueDB(endpoint.nodeId).getValue<{ + return ctx.getValueDB(endpoint.nodeId).getValue<{ profile: AssociationGroupInfoProfile; }>( AssociationGroupInfoCCValues.groupInfo(groupId).endpoint( @@ -286,11 +286,11 @@ export class AssociationGroupInfoCC extends CommandClass { /** Returns the dictionary of all commands issued by the given association group */ public static getIssuedCommandsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, groupId: number, ): MaybeNotKnown> { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( AssociationGroupInfoCCValues.commands(groupId).endpoint( @@ -300,20 +300,20 @@ export class AssociationGroupInfoCC extends CommandClass { } public static findGroupsForIssuedCommand( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId & SupportsCC, ccId: CommandClasses, command: number, ): number[] { const ret: number[] = []; const associationGroupCount = this.getAssociationGroupCountCached( - applHost, + ctx, endpoint, ); for (let groupId = 1; groupId <= associationGroupCount; groupId++) { // Scan the issued commands of all groups if there's a match const issuedCommands = this.getIssuedCommandsCached( - applHost, + ctx, endpoint, groupId, ); @@ -330,37 +330,41 @@ export class AssociationGroupInfoCC extends CommandClass { } private static getAssociationGroupCountCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId & SupportsCC, ): number { // The association group count is either determined by the // Association CC or the Multi Channel Association CC return ( // First query the Multi Channel Association CC - (endpoint.supportsCC(CommandClasses["Multi Channel Association"]) + // And fall back to 0 + (endpoint.supportsCC( + CommandClasses["Multi Channel Association"], + ) && MultiChannelAssociationCC.getGroupCountCached( - applHost, + ctx, endpoint, )) // Then the Association CC || (endpoint.supportsCC(CommandClasses.Association) - && AssociationCC.getGroupCountCached(applHost, endpoint)) - // And fall back to 0 + && AssociationCC.getGroupCountCached(ctx, endpoint)) || 0 ); } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Association Group Information"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -368,13 +372,13 @@ export class AssociationGroupInfoCC extends CommandClass { const associationGroupCount = AssociationGroupInfoCC .getAssociationGroupCountCached( - applHost, + ctx, endpoint, ); for (let groupId = 1; groupId <= associationGroupCount; groupId++) { // First get the group's name - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Association group #${groupId}: Querying name...`, direction: "outbound", @@ -383,7 +387,7 @@ export class AssociationGroupInfoCC extends CommandClass { if (name) { const logMessage = `Association group #${groupId} has name "${name}"`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -391,7 +395,7 @@ export class AssociationGroupInfoCC extends CommandClass { } // Then the command list - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Association group #${groupId}: Querying command list...`, @@ -402,35 +406,37 @@ export class AssociationGroupInfoCC extends CommandClass { } // Finally query each group for its information - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Association Group Information"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery }); // Query the information for each group (this is the only thing that could be dynamic) const associationGroupCount = AssociationGroupInfoCC .getAssociationGroupCountCached( - applHost, + ctx, endpoint, ); const hasDynamicInfo = this.getValue( - applHost, + ctx, AssociationGroupInfoCCValues.hasDynamicInfo, ); for (let groupId = 1; groupId <= associationGroupCount; groupId++) { // Then its information - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Association group #${groupId}: Querying info...`, direction: "outbound", @@ -446,7 +452,7 @@ profile: ${ info.profile, ) }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -467,12 +473,11 @@ export interface AssociationGroupInfoCCNameReportOptions @CCCommand(AssociationGroupInfoCommand.NameReport) export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationGroupInfoCCNameReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -493,9 +498,9 @@ export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { public readonly groupId: number; public readonly name: string; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; - const valueDB = this.getValueDB(applHost); + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + const valueDB = this.getValueDB(ctx); valueDB.setValue( AssociationGroupInfoCCValues.groupName(this.groupId).endpoint( @@ -507,17 +512,17 @@ export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.groupId, this.name.length]), Buffer.from(this.name, "utf8"), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, name: this.name, @@ -535,12 +540,11 @@ export interface AssociationGroupInfoCCNameGetOptions extends CCCommandOptions { @expectedCCResponse(AssociationGroupInfoCCNameReport) export class AssociationGroupInfoCCNameGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationGroupInfoCCNameGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.groupId = this.payload[0]; @@ -551,14 +555,14 @@ export class AssociationGroupInfoCCNameGet extends AssociationGroupInfoCC { public groupId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId }, }; } @@ -581,7 +585,6 @@ export interface AssociationGroupInfoCCInfoReportSpecificOptions { @CCCommand(AssociationGroupInfoCommand.InfoReport) export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ( @@ -589,7 +592,7 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { & CCCommandOptions ), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -625,13 +628,13 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { public readonly groups: readonly AssociationGroupInfo[]; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; for (const group of this.groups) { const { groupId, mode, profile, eventCode } = group; this.setValue( - applHost, + ctx, AssociationGroupInfoCCValues.groupInfo(groupId), { mode, @@ -643,7 +646,7 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.alloc(1 + this.groups.length * 7, 0); this.payload[0] = (this.isListMode ? 0b1000_0000 : 0) @@ -657,12 +660,12 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { // The remaining bytes are zero } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "is list mode": this.isListMode, "has dynamic info": this.hasDynamicInfo, @@ -701,12 +704,11 @@ export type AssociationGroupInfoCCInfoGetOptions = @expectedCCResponse(AssociationGroupInfoCCInfoReport) export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationGroupInfoCCInfoGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); const optionByte = this.payload[0]; @@ -726,7 +728,7 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { public listMode?: boolean; public groupId?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const isListMode = this.listMode === true; const optionByte = (this.refreshCache ? 0b1000_0000 : 0) | (isListMode ? 0b0100_0000 : 0); @@ -734,10 +736,10 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { optionByte, isListMode ? 0 : this.groupId!, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.groupId != undefined) { message["group id"] = this.groupId; @@ -747,7 +749,7 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { } message["refresh cache"] = this.refreshCache; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -766,12 +768,11 @@ export class AssociationGroupInfoCCCommandListReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationGroupInfoCCCommandListReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -806,7 +807,7 @@ export class AssociationGroupInfoCCCommandListReport ) public readonly commands: ReadonlyMap; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // To make it easier to encode possible extended CCs, we first // allocate as much space as we may need, then trim it again this.payload = Buffer.allocUnsafe(2 + this.commands.size * 3); @@ -821,12 +822,12 @@ export class AssociationGroupInfoCCCommandListReport } this.payload[1] = offset - 2; // list length - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, commands: `${ @@ -859,12 +860,11 @@ export class AssociationGroupInfoCCCommandListGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | AssociationGroupInfoCCCommandListGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.allowCache = !!(this.payload[0] & 0b1000_0000); @@ -878,17 +878,17 @@ export class AssociationGroupInfoCCCommandListGet public allowCache: boolean; public groupId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.allowCache ? 0b1000_0000 : 0, this.groupId, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "allow cache": this.allowCache, diff --git a/packages/cc/src/cc/BarrierOperatorCC.ts b/packages/cc/src/cc/BarrierOperatorCC.ts index f7b245eb941f..0a15deaa326d 100644 --- a/packages/cc/src/cc/BarrierOperatorCC.ts +++ b/packages/cc/src/cc/BarrierOperatorCC.ts @@ -14,11 +14,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, isEnumMember, @@ -43,6 +39,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -146,11 +145,11 @@ export class BarrierOperatorCCAPI extends CCAPI { BarrierOperatorCommand.Get, ); - const cc = new BarrierOperatorCCGet(this.applHost, { + const cc = new BarrierOperatorCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BarrierOperatorCCReport >( cc, @@ -170,12 +169,12 @@ export class BarrierOperatorCCAPI extends CCAPI { BarrierOperatorCommand.Set, ); - const cc = new BarrierOperatorCCSet(this.applHost, { + const cc = new BarrierOperatorCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, targetState, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -187,14 +186,11 @@ export class BarrierOperatorCCAPI extends CCAPI { BarrierOperatorCommand.SignalingCapabilitiesGet, ); - const cc = new BarrierOperatorCCSignalingCapabilitiesGet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - }, - ); - const response = await this.applHost.sendCommand< + const cc = new BarrierOperatorCCSignalingCapabilitiesGet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + }); + const response = await this.host.sendCommand< BarrierOperatorCCSignalingCapabilitiesReport >( cc, @@ -212,12 +208,12 @@ export class BarrierOperatorCCAPI extends CCAPI { BarrierOperatorCommand.EventSignalingGet, ); - const cc = new BarrierOperatorCCEventSignalingGet(this.applHost, { + const cc = new BarrierOperatorCCEventSignalingGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, subsystemType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BarrierOperatorCCEventSignalingReport >( cc, @@ -236,14 +232,14 @@ export class BarrierOperatorCCAPI extends CCAPI { BarrierOperatorCommand.EventSignalingSet, ); - const cc = new BarrierOperatorCCEventSignalingSet(this.applHost, { + const cc = new BarrierOperatorCCEventSignalingSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, subsystemType, subsystemState, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -353,7 +349,7 @@ export class BarrierOperatorCCAPI extends CCAPI { ); // and optimistically update the currentValue for (const node of affectedNodes) { - this.applHost + this.host .tryGetValueDB(node.id) ?.setValue(currentStateValueId, targetValue); } @@ -422,33 +418,35 @@ export class BarrierOperatorCCAPI extends CCAPI { export class BarrierOperatorCC extends CommandClass { declare ccCommand: BarrierOperatorCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Barrier Operator"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Create targetState value if it does not exist - this.ensureMetadata(applHost, BarrierOperatorCCValues.targetState); + this.ensureMetadata(ctx, BarrierOperatorCCValues.targetState); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "Querying signaling capabilities...", direction: "outbound", }); const resp = await api.getSignalingCapabilities(); if (resp) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Received supported subsystem types: ${ resp .map((t) => @@ -465,7 +463,7 @@ export class BarrierOperatorCC extends CommandClass { // for valid values and throws otherwise. if (!isEnumMember(SubsystemType, subsystemType)) continue; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Enabling subsystem ${ getEnumMemberName( SubsystemType, @@ -479,25 +477,27 @@ export class BarrierOperatorCC extends CommandClass { } } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Barrier Operator"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const supportedSubsystems: SubsystemType[] = this.getValue( - applHost, + ctx, BarrierOperatorCCValues.supportedSubsystemTypes, ) ?? []; @@ -506,7 +506,7 @@ export class BarrierOperatorCC extends CommandClass { // for valid values and throws otherwise. if (!isEnumMember(SubsystemType, subsystemType)) continue; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Querying event signaling state for subsystem ${ getEnumMemberName( SubsystemType, @@ -517,7 +517,7 @@ export class BarrierOperatorCC extends CommandClass { }); const state = await api.getEventSignaling(subsystemType); if (state != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Subsystem ${ getEnumMemberName( SubsystemType, @@ -529,7 +529,7 @@ export class BarrierOperatorCC extends CommandClass { } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying current barrier state...", direction: "outbound", }); @@ -546,12 +546,11 @@ export interface BarrierOperatorCCSetOptions extends CCCommandOptions { @useSupervision() export class BarrierOperatorCCSet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | BarrierOperatorCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -564,14 +563,14 @@ export class BarrierOperatorCCSet extends BarrierOperatorCC { public targetState: BarrierState.Open | BarrierState.Closed; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.targetState]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "target state": this.targetState }, }; } @@ -580,10 +579,9 @@ export class BarrierOperatorCCSet extends BarrierOperatorCC { @CCCommand(BarrierOperatorCommand.Report) export class BarrierOperatorCCReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); @@ -621,9 +619,9 @@ export class BarrierOperatorCCReport extends BarrierOperatorCC { @ccValue(BarrierOperatorCCValues.position) public readonly position: MaybeUnknown; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "barrier position": maybeUnknownToString(this.position), "barrier state": this.currentState != undefined @@ -643,10 +641,9 @@ export class BarrierOperatorCCSignalingCapabilitiesReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); this.supportedSubsystemTypes = parseBitMask( this.payload, @@ -657,9 +654,9 @@ export class BarrierOperatorCCSignalingCapabilitiesReport @ccValue(BarrierOperatorCCValues.supportedSubsystemTypes) public readonly supportedSubsystemTypes: readonly SubsystemType[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported types": this.supportedSubsystemTypes .map((t) => `\n· ${getEnumMemberName(SubsystemType, t)}`) @@ -687,12 +684,11 @@ export interface BarrierOperatorCCEventSignalingSetOptions @useSupervision() export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | BarrierOperatorCCEventSignalingSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -707,14 +703,14 @@ export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { public subsystemType: SubsystemType; public subsystemState: SubsystemState; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.subsystemType, this.subsystemState]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "subsystem type": getEnumMemberName( SubsystemType, @@ -732,25 +728,24 @@ export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { @CCCommand(BarrierOperatorCommand.EventSignalingReport) export class BarrierOperatorCCEventSignalingReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.subsystemType = this.payload[0]; this.subsystemState = this.payload[1]; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const signalingStateValue = BarrierOperatorCCValues.signalingState( this.subsystemType, ); - this.ensureMetadata(applHost, signalingStateValue); - this.setValue(applHost, signalingStateValue, this.subsystemState); + this.ensureMetadata(ctx, signalingStateValue); + this.setValue(ctx, signalingStateValue, this.subsystemState); return true; } @@ -758,9 +753,9 @@ export class BarrierOperatorCCEventSignalingReport extends BarrierOperatorCC { public readonly subsystemType: SubsystemType; public readonly subsystemState: SubsystemState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "subsystem type": getEnumMemberName( SubsystemType, @@ -786,12 +781,11 @@ export interface BarrierOperatorCCEventSignalingGetOptions @expectedCCResponse(BarrierOperatorCCEventSignalingReport) export class BarrierOperatorCCEventSignalingGet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | BarrierOperatorCCEventSignalingGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -805,14 +799,14 @@ export class BarrierOperatorCCEventSignalingGet extends BarrierOperatorCC { public subsystemType: SubsystemType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.subsystemType]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "subsystem type": getEnumMemberName( SubsystemType, diff --git a/packages/cc/src/cc/BasicCC.ts b/packages/cc/src/cc/BasicCC.ts index 832be1af0264..b7746565e656 100644 --- a/packages/cc/src/cc/BasicCC.ts +++ b/packages/cc/src/cc/BasicCC.ts @@ -1,12 +1,17 @@ import { CommandClasses, + type ControlsCC, Duration, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, type MaybeUnknown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, type SupervisionResult, + type SupportsCC, type ValueID, ValueMetadata, maybeUnknownToString, @@ -14,9 +19,11 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { type AllOrNone, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -35,6 +42,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -167,7 +178,7 @@ export class BasicCCAPI extends CCAPI { ); // and optimistically update the currentValue for (const node of affectedNodes) { - this.applHost + this.host .tryGetValueDB(node.id) ?.setValue(currentValueValueId, value); } @@ -213,11 +224,11 @@ export class BasicCCAPI extends CCAPI { public async get() { this.assertSupportsCommand(BasicCommand, BasicCommand.Get); - const cc = new BasicCCGet(this.applHost, { + const cc = new BasicCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -236,12 +247,12 @@ export class BasicCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(BasicCommand, BasicCommand.Set); - const cc = new BasicCCSet(this.applHost, { + const cc = new BasicCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, targetValue, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -251,11 +262,13 @@ export class BasicCCAPI extends CCAPI { export class BasicCC extends CommandClass { declare ccCommand: BasicCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -265,13 +278,13 @@ export class BasicCC extends CommandClass { endpoint.addCC(CommandClasses.Basic, { isSupported: true }); // try to query the current state - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remove Basic CC support again when there was no response if ( - this.getValue(applHost, BasicCCValues.currentValue) == undefined + this.getValue(ctx, BasicCCValues.currentValue) == undefined ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "No response to Basic Get command, assuming Basic CC is unsupported...", @@ -285,22 +298,24 @@ export class BasicCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Basic, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // try to query the current state - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying Basic CC state...", direction: "outbound", @@ -315,7 +330,7 @@ current value: ${basicResponse.currentValue}`; target value: ${basicResponse.targetValue} remaining duration: ${basicResponse.duration?.toString() ?? "undefined"}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -324,12 +339,18 @@ remaining duration: ${basicResponse.duration?.toString() ?? "undefined"}`; } public override getDefinedValueIDs( - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): ValueID[] { const ret: ValueID[] = []; - const endpoint = this.getEndpoint(applHost)!; + const endpoint = this.getEndpoint(ctx)!; - const compat = applHost.getDeviceConfig?.(endpoint.nodeId)?.compat; + const compat = ctx.getDeviceConfig?.(endpoint.nodeId)?.compat; if (compat?.mapBasicSet === "event") { // Add the compat event value if it should be exposed ret.push(BasicCCValues.compatEvent.endpoint(endpoint.index)); @@ -338,7 +359,7 @@ remaining duration: ${basicResponse.duration?.toString() ?? "undefined"}`; if (endpoint.supportsCC(this.ccId)) { // Defer to the base implementation if Basic CC is supported. // This implies that no other actuator CC is supported. - ret.push(...super.getDefinedValueIDs(applHost)); + ret.push(...super.getDefinedValueIDs(ctx)); } else if (endpoint.controlsCC(CommandClasses.Basic)) { // During the interview, we mark Basic CC as controlled only if we want to expose currentValue ret.push(BasicCCValues.currentValue.endpoint(endpoint.index)); @@ -357,10 +378,9 @@ export interface BasicCCSetOptions extends CCCommandOptions { @useSupervision() export class BasicCCSet extends BasicCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | BasicCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.targetValue = this.payload[0]; @@ -371,14 +391,14 @@ export class BasicCCSet extends BasicCC { public targetValue: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.targetValue]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "target value": this.targetValue }, }; } @@ -399,10 +419,9 @@ export type BasicCCReportOptions = export class BasicCCReport extends BasicCC { // @noCCValues See comment in the constructor public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | BasicCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -437,14 +456,14 @@ export class BasicCCReport extends BasicCC { @ccValue(BasicCCValues.duration) public readonly duration: Duration | undefined; - public persistValues(applHost: ZWaveApplicationHost): boolean { + public persistValues(ctx: PersistValuesContext): boolean { // Basic CC Report persists its values itself, since there are some // specific rules when which value may be persisted. // These rules are essentially encoded in the getDefinedValueIDs overload, // so we simply reuse that here. // Figure out which values may be persisted. - const definedValueIDs = this.getDefinedValueIDs(applHost); + const definedValueIDs = this.getDefinedValueIDs(ctx); const shouldPersistCurrentValue = definedValueIDs.some((vid) => BasicCCValues.currentValue.is(vid) ); @@ -457,21 +476,21 @@ export class BasicCCReport extends BasicCC { if (this.currentValue !== undefined && shouldPersistCurrentValue) { this.setValue( - applHost, + ctx, BasicCCValues.currentValue, this.currentValue, ); } if (this.targetValue !== undefined && shouldPersistTargetValue) { this.setValue( - applHost, + ctx, BasicCCValues.targetValue, this.targetValue, ); } if (this.duration !== undefined && shouldPersistDuration) { this.setValue( - applHost, + ctx, BasicCCValues.duration, this.duration, ); @@ -480,15 +499,16 @@ export class BasicCCReport extends BasicCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.currentValue ?? 0xfe, this.targetValue ?? 0xfe, (this.duration ?? Duration.unknown()).serializeReport(), ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -496,10 +516,10 @@ export class BasicCCReport extends BasicCC { this.payload = this.payload.subarray(0, 1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "current value": maybeUnknownToString(this.currentValue), }; @@ -510,7 +530,7 @@ export class BasicCCReport extends BasicCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/BatteryCC.ts b/packages/cc/src/cc/BatteryCC.ts index eb96dc960623..b4bb1a462cfa 100644 --- a/packages/cc/src/cc/BatteryCC.ts +++ b/packages/cc/src/cc/BatteryCC.ts @@ -1,8 +1,13 @@ import { timespan } from "@zwave-js/core"; import type { + ControlsCC, + EndpointId, + GetEndpoint, MessageOrCCLogEntry, MessageRecord, + NodeId, SinglecastCC, + SupportsCC, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -14,9 +19,11 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { type AllOrNone, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { @@ -30,6 +37,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -228,11 +238,11 @@ export class BatteryCCAPI extends PhysicalCCAPI { public async get() { this.assertSupportsCommand(BatteryCommand, BatteryCommand.Get); - const cc = new BatteryCCGet(this.applHost, { + const cc = new BatteryCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -256,11 +266,11 @@ export class BatteryCCAPI extends PhysicalCCAPI { public async getHealth() { this.assertSupportsCommand(BatteryCommand, BatteryCommand.HealthGet); - const cc = new BatteryCCHealthGet(this.applHost, { + const cc = new BatteryCCHealthGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -276,34 +286,38 @@ export class BatteryCCAPI extends PhysicalCCAPI { export class BatteryCC extends CommandClass { declare ccCommand: BatteryCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Query the Battery status - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Battery, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying battery status...", direction: "outbound", @@ -315,7 +329,7 @@ export class BatteryCC extends CommandClass { level: ${batteryStatus.level}${ batteryStatus.isLow ? " (low)" : "" }`; - if (this.version >= 2) { + if (api.version >= 2) { logMessage += ` status: ${ BatteryChargingStatus[batteryStatus.chargingStatus!] @@ -330,16 +344,16 @@ needs to be replaced or charged: ${ is low temperature ${batteryStatus.lowTemperatureStatus} is disconnected: ${batteryStatus.disconnected}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } - if (this.version >= 2) { + if (api.version >= 2) { // always query the health - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying battery health...", direction: "outbound", @@ -350,7 +364,7 @@ is disconnected: ${batteryStatus.disconnected}`; const logMessage = `received response for battery health: max. capacity: ${batteryHealth.maximumCapacity} % temperature: ${batteryHealth.temperature} °C`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -361,10 +375,16 @@ temperature: ${batteryHealth.temperature} °C`; public shouldRefreshValues( this: SinglecastCC, - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): boolean { // Check when the battery state was last updated - const valueDB = applHost.tryGetValueDB(this.nodeId); + const valueDB = ctx.tryGetValueDB(this.nodeId); if (!valueDB) return true; const lastUpdated = valueDB.getTimestamp( @@ -410,10 +430,9 @@ export type BatteryCCReportOptions = @CCCommand(BatteryCommand.Report) export class BatteryCCReport extends BatteryCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | BatteryCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -454,15 +473,15 @@ export class BatteryCCReport extends BatteryCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Naïve heuristic for a full battery if (this.level >= 90) { // Some devices send Notification CC Reports with battery information, // or this information is mapped from legacy V1 alarm values. // We may need to idle the corresponding values when the battery is full - const notificationCCVersion = applHost.getSupportedCCVersion( + const notificationCCVersion = ctx.getSupportedCCVersion( CommandClasses.Notification, this.nodeId as number, this.endpointIndex, @@ -479,9 +498,9 @@ export class BatteryCCReport extends BatteryCC { "Battery level status", ); // If not undefined and not idle - if (this.getValue(applHost, batteryLevelStatusValue)) { + if (this.getValue(ctx, batteryLevelStatusValue)) { this.setValue( - applHost, + ctx, batteryLevelStatusValue, 0, /* idle */ ); @@ -522,7 +541,7 @@ export class BatteryCCReport extends BatteryCC { @ccValue(BatteryCCValues.lowTemperatureStatus) public readonly lowTemperatureStatus: boolean | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.isLow ? 0xff : this.level]); if (this.chargingStatus != undefined) { this.payload = Buffer.concat([ @@ -544,10 +563,10 @@ export class BatteryCCReport extends BatteryCC { ]), ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { level: this.level, "is low": this.isLow, @@ -583,7 +602,7 @@ export class BatteryCCReport extends BatteryCC { message.disconnected = this.disconnected; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -596,10 +615,9 @@ export class BatteryCCGet extends BatteryCC {} @CCCommand(BatteryCommand.HealthReport) export class BatteryCCHealthReport extends BatteryCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); @@ -615,12 +633,12 @@ export class BatteryCCHealthReport extends BatteryCC { this.temperatureScale = scale; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Update the temperature unit in the value DB const temperatureValue = BatteryCCValues.temperature; - this.setMetadata(applHost, temperatureValue, { + this.setMetadata(ctx, temperatureValue, { ...temperatureValue.meta, unit: this.temperatureScale === 0x00 ? "°C" : undefined, }); @@ -636,9 +654,9 @@ export class BatteryCCHealthReport extends BatteryCC { private readonly temperatureScale: number | undefined; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { temperature: this.temperature != undefined ? this.temperature diff --git a/packages/cc/src/cc/BinarySensorCC.ts b/packages/cc/src/cc/BinarySensorCC.ts index 45fa98d6bb11..49afdf1ce7df 100644 --- a/packages/cc/src/cc/BinarySensorCC.ts +++ b/packages/cc/src/cc/BinarySensorCC.ts @@ -1,6 +1,6 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -10,11 +10,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, isEnumMember } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -28,6 +24,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -111,12 +110,12 @@ export class BinarySensorCCAPI extends PhysicalCCAPI { BinarySensorCommand.Get, ); - const cc = new BinarySensorCCGet(this.applHost, { + const cc = new BinarySensorCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sensorType, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -134,13 +133,13 @@ export class BinarySensorCCAPI extends PhysicalCCAPI { BinarySensorCommand.Report, ); - const cc = new BinarySensorCCReport(this.applHost, { + const cc = new BinarySensorCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, value, type: sensorType, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupportedSensorTypes(): Promise< @@ -151,11 +150,11 @@ export class BinarySensorCCAPI extends PhysicalCCAPI { BinarySensorCommand.SupportedGet, ); - const cc = new BinarySensorCCSupportedGet(this.applHost, { + const cc = new BinarySensorCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BinarySensorCCSupportedReport >( cc, @@ -174,12 +173,12 @@ export class BinarySensorCCAPI extends PhysicalCCAPI { BinarySensorCommand.SupportedReport, ); - const cc = new BinarySensorCCSupportedReport(this.applHost, { + const cc = new BinarySensorCCSupportedReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, supportedSensorTypes: supported, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -189,26 +188,28 @@ export class BinarySensorCCAPI extends PhysicalCCAPI { export class BinarySensorCC extends CommandClass { declare ccCommand: BinarySensorCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Binary Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Find out which sensor types this sensor supports - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported sensor types...", direction: "outbound", @@ -223,13 +224,13 @@ export class BinarySensorCC extends CommandClass { .map((name) => `\n· ${name}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported sensor types timed out, skipping interview...", @@ -239,33 +240,35 @@ export class BinarySensorCC extends CommandClass { } } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Binary Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query (all of) the sensor's current value(s) - if (this.version === 1) { - applHost.controllerLog.logNode(node.id, { + if (api.version === 1) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current value...", direction: "outbound", }); const currentValue = await api.get(); if (currentValue != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received current value: ${currentValue}`, direction: "inbound", @@ -274,7 +277,7 @@ export class BinarySensorCC extends CommandClass { } else { const supportedSensorTypes: readonly BinarySensorType[] = this.getValue( - applHost, + ctx, BinarySensorCCValues.supportedSensorTypes, ) ?? []; @@ -284,14 +287,14 @@ export class BinarySensorCC extends CommandClass { if (!isEnumMember(BinarySensorType, type)) continue; const sensorName = getEnumMemberName(BinarySensorType, type); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current value for ${sensorName}...`, direction: "outbound", }); const currentValue = await api.get(type); if (currentValue != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received current value for ${sensorName}: ${currentValue}`, @@ -307,10 +310,10 @@ export class BinarySensorCC extends CommandClass { * This only works AFTER the interview process */ public static getSupportedSensorTypesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( BinarySensorCCValues.supportedSensorTypes.endpoint( @@ -320,11 +323,11 @@ export class BinarySensorCC extends CommandClass { } public setMappedBasicValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, value: number, ): boolean { this.setValue( - applHost, + ctx, BinarySensorCCValues.state(BinarySensorType.Any), value > 0, ); @@ -341,12 +344,11 @@ export interface BinarySensorCCReportOptions extends CCCommandOptions { @CCCommand(BinarySensorCommand.Report) export class BinarySensorCCReport extends BinarySensorCC { public constructor( - host: ZWaveHost, options: | BinarySensorCCReportOptions | CommandClassDeserializationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -361,14 +363,14 @@ export class BinarySensorCCReport extends BinarySensorCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Workaround for devices reporting with sensor type Any -> find first supported sensor type and use that let sensorType = this.type; if (sensorType === BinarySensorType.Any) { const supportedSensorTypes = this.getValue( - applHost, + ctx, BinarySensorCCValues.supportedSensorTypes, ); if (supportedSensorTypes?.length) { @@ -377,8 +379,8 @@ export class BinarySensorCCReport extends BinarySensorCC { } const binarySensorValue = BinarySensorCCValues.state(sensorType); - this.setMetadata(applHost, binarySensorValue, binarySensorValue.meta); - this.setValue(applHost, binarySensorValue, this.value); + this.setMetadata(ctx, binarySensorValue, binarySensorValue.meta); + this.setValue(ctx, binarySensorValue, this.value); return true; } @@ -386,14 +388,14 @@ export class BinarySensorCCReport extends BinarySensorCC { public type: BinarySensorType; public value: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.value ? 0xff : 0x00, this.type]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { type: getEnumMemberName(BinarySensorType, this.type), value: this.value, @@ -425,10 +427,9 @@ export interface BinarySensorCCGetOptions extends CCCommandOptions { @expectedCCResponse(BinarySensorCCReport, testResponseForBinarySensorGet) export class BinarySensorCCGet extends BinarySensorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | BinarySensorCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length >= 1) { this.sensorType = this.payload[0]; @@ -440,14 +441,14 @@ export class BinarySensorCCGet extends BinarySensorCC { public sensorType: BinarySensorType | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sensorType ?? BinarySensorType.Any]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { type: getEnumMemberName( BinarySensorType, @@ -466,12 +467,11 @@ export interface BinarySensorCCSupportedReportOptions { @CCCommand(BinarySensorCommand.SupportedReport) export class BinarySensorCCSupportedReport extends BinarySensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (BinarySensorCCSupportedReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -488,18 +488,18 @@ export class BinarySensorCCSupportedReport extends BinarySensorCC { @ccValue(BinarySensorCCValues.supportedSensorTypes) public supportedSensorTypes: BinarySensorType[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeBitMask( this.supportedSensorTypes.filter((t) => t !== BinarySensorType.Any), undefined, 0, ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported types": this.supportedSensorTypes .map((type) => getEnumMemberName(BinarySensorType, type)) diff --git a/packages/cc/src/cc/BinarySwitchCC.ts b/packages/cc/src/cc/BinarySwitchCC.ts index 9674ec342873..9442d2938386 100644 --- a/packages/cc/src/cc/BinarySwitchCC.ts +++ b/packages/cc/src/cc/BinarySwitchCC.ts @@ -14,11 +14,7 @@ import { parseMaybeBoolean, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import type { AllOrNone } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -36,6 +32,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -100,11 +99,11 @@ export class BinarySwitchCCAPI extends CCAPI { BinarySwitchCommand.Get, ); - const cc = new BinarySwitchCCGet(this.applHost, { + const cc = new BinarySwitchCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -133,13 +132,13 @@ export class BinarySwitchCCAPI extends CCAPI { BinarySwitchCommand.Set, ); - const cc = new BinarySwitchCCSet(this.applHost, { + const cc = new BinarySwitchCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, targetValue, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -194,7 +193,7 @@ export class BinarySwitchCCAPI extends CCAPI { ); // and optimistically update the currentValue for (const node of affectedNodes) { - this.applHost + this.host .tryGetValueDB(node.id) ?.setValue(currentValueValueId, value); } @@ -239,34 +238,38 @@ export class BinarySwitchCCAPI extends CCAPI { export class BinarySwitchCC extends CommandClass { declare ccCommand: BinarySwitchCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Binary Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current state - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying Binary Switch state...", direction: "outbound", @@ -281,7 +284,7 @@ current value: ${resp.currentValue}`; target value: ${resp.targetValue} remaining duration: ${resp.duration?.toString() ?? "undefined"}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -290,10 +293,10 @@ remaining duration: ${resp.duration?.toString() ?? "undefined"}`; } public setMappedBasicValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, value: number, ): boolean { - this.setValue(applHost, BinarySwitchCCValues.currentValue, value > 0); + this.setValue(ctx, BinarySwitchCCValues.currentValue, value > 0); return true; } } @@ -308,10 +311,9 @@ export interface BinarySwitchCCSetOptions extends CCCommandOptions { @useSupervision() export class BinarySwitchCCSet extends BinarySwitchCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | BinarySwitchCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.targetValue = !!this.payload[0]; @@ -327,14 +329,15 @@ export class BinarySwitchCCSet extends BinarySwitchCC { public targetValue: boolean; public duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.targetValue ? 0xff : 0x00, (this.duration ?? Duration.default()).serializeSet(), ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -342,10 +345,10 @@ export class BinarySwitchCCSet extends BinarySwitchCC { this.payload = this.payload.subarray(0, 1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "target value": this.targetValue, }; @@ -353,7 +356,7 @@ export class BinarySwitchCCSet extends BinarySwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -373,12 +376,11 @@ export type BinarySwitchCCReportOptions = @CCCommand(BinarySwitchCommand.Report) export class BinarySwitchCCReport extends BinarySwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | BinarySwitchCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -404,7 +406,7 @@ export class BinarySwitchCCReport extends BinarySwitchCC { @ccValue(BinarySwitchCCValues.duration) public readonly duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ encodeMaybeBoolean(this.currentValue ?? UNKNOWN_STATE), ]); @@ -417,10 +419,10 @@ export class BinarySwitchCCReport extends BinarySwitchCC { ]), ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "current value": maybeUnknownToString(this.currentValue), }; @@ -431,7 +433,7 @@ export class BinarySwitchCCReport extends BinarySwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/CRC16CC.ts b/packages/cc/src/cc/CRC16CC.ts index 22d490ac013e..fe0a30c8437e 100644 --- a/packages/cc/src/cc/CRC16CC.ts +++ b/packages/cc/src/cc/CRC16CC.ts @@ -6,7 +6,7 @@ import { type MessageOrCCLogEntry, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { CCAPI } from "../lib/API"; import { type CCCommandOptions, @@ -46,12 +46,12 @@ export class CRC16CCAPI extends CCAPI { CRC16Command.CommandEncapsulation, ); - const cc = new CRC16CCCommandEncapsulation(this.applHost, { + const cc = new CRC16CCCommandEncapsulation({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, encapsulated: encapsulatedCC, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -70,10 +70,9 @@ export class CRC16CC extends CommandClass { /** Encapsulates a command in a CRC-16 CC */ public static encapsulate( - host: ZWaveHost, cc: CommandClass, ): CRC16CCCommandEncapsulation { - const ret = new CRC16CCCommandEncapsulation(host, { + const ret = new CRC16CCCommandEncapsulation({ nodeId: cc.nodeId, encapsulated: cc, }); @@ -107,12 +106,11 @@ function getCCResponseForCommandEncapsulation( ) export class CRC16CCCommandEncapsulation extends CRC16CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | CRC16CCCommandEncapsulationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -126,12 +124,12 @@ export class CRC16CCCommandEncapsulation extends CRC16CC { ); validatePayload(expectedCRC === actualCRC); - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: ccBuffer, fromEncapsulation: true, encapCC: this, origin: options.origin, - frameType: options.frameType, + context: options.context, }); } else { this.encapsulated = options.encapsulated; @@ -142,8 +140,8 @@ export class CRC16CCCommandEncapsulation extends CRC16CC { public encapsulated: CommandClass; private readonly headerBuffer = Buffer.from([this.ccId, this.ccCommand]); - public serialize(): Buffer { - const commandBuffer = this.encapsulated.serialize(); + public serialize(ctx: CCEncodingContext): Buffer { + const commandBuffer = this.encapsulated.serialize(ctx); // Reserve 2 bytes for the CRC this.payload = Buffer.concat([commandBuffer, Buffer.allocUnsafe(2)]); @@ -153,7 +151,7 @@ export class CRC16CCCommandEncapsulation extends CRC16CC { crc = CRC16_CCITT(commandBuffer, crc); this.payload.writeUInt16BE(crc, this.payload.length - 2); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -161,9 +159,9 @@ export class CRC16CCCommandEncapsulation extends CRC16CC { return super.computeEncapsulationOverhead() + 2; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), // Hide the default payload line message: undefined, }; diff --git a/packages/cc/src/cc/CentralSceneCC.ts b/packages/cc/src/cc/CentralSceneCC.ts index d50422e5a3f7..452b9f4da212 100644 --- a/packages/cc/src/cc/CentralSceneCC.ts +++ b/packages/cc/src/cc/CentralSceneCC.ts @@ -14,11 +14,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -35,6 +31,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -113,11 +111,11 @@ export class CentralSceneCCAPI extends CCAPI { CentralSceneCommand.SupportedGet, ); - const cc = new CentralSceneCCSupportedGet(this.applHost, { + const cc = new CentralSceneCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< CentralSceneCCSupportedReport >( cc, @@ -139,11 +137,11 @@ export class CentralSceneCCAPI extends CCAPI { CentralSceneCommand.ConfigurationGet, ); - const cc = new CentralSceneCCConfigurationGet(this.applHost, { + const cc = new CentralSceneCCConfigurationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< CentralSceneCCConfigurationReport >( cc, @@ -163,12 +161,12 @@ export class CentralSceneCCAPI extends CCAPI { CentralSceneCommand.ConfigurationSet, ); - const cc = new CentralSceneCCConfigurationSet(this.applHost, { + const cc = new CentralSceneCCConfigurationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, slowRefresh, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -218,18 +216,20 @@ export class CentralSceneCC extends CommandClass { return true; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Central Scene"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -239,13 +239,13 @@ export class CentralSceneCC extends CommandClass { // we must associate ourselves with that channel try { await ccUtils.assignLifelineIssueingCommand( - applHost, + ctx, endpoint, this.ccId, CentralSceneCommand.Notification, ); } catch { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Configuring associations to receive ${ getCCName( @@ -256,7 +256,7 @@ export class CentralSceneCC extends CommandClass { }); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported scenes...", direction: "outbound", @@ -266,13 +266,13 @@ export class CentralSceneCC extends CommandClass { const logMessage = `received supported scenes: # of scenes: ${ccSupported.sceneCount} supports slow refresh: ${ccSupported.supportsSlowRefresh}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported scenes timed out, skipping interview...", @@ -282,8 +282,8 @@ supports slow refresh: ${ccSupported.supportsSlowRefresh}`; } // The slow refresh capability should be enabled whenever possible - if (this.version >= 3 && ccSupported?.supportsSlowRefresh) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 3 && ccSupported?.supportsSlowRefresh) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Enabling slow refresh capability...", direction: "outbound", @@ -292,38 +292,34 @@ supports slow refresh: ${ccSupported.supportsSlowRefresh}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @CCCommand(CentralSceneCommand.Notification) export class CentralSceneCCNotification extends CentralSceneCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 3); this.sequenceNumber = this.payload[0]; this.keyAttribute = this.payload[1] & 0b111; this.sceneNumber = this.payload[2]; - if ( - this.keyAttribute === CentralSceneKeys.KeyHeldDown - && this.version >= 3 - ) { + if (this.keyAttribute === CentralSceneKeys.KeyHeldDown) { // A receiving node MUST ignore this field if the command is not // carrying the Key Held Down key attribute. this.slowRefresh = !!(this.payload[1] & 0b1000_0000); } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // In case the interview is not yet completed, we still create some basic metadata const sceneValue = CentralSceneCCValues.scene(this.sceneNumber); - this.ensureMetadata(applHost, sceneValue); + this.ensureMetadata(ctx, sceneValue); // The spec behavior is pretty complicated, so we cannot just store // the value and call it a day. Handling of these notifications will @@ -337,7 +333,7 @@ export class CentralSceneCCNotification extends CentralSceneCC { public readonly sceneNumber: number; public readonly slowRefresh: boolean | undefined; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "sequence number": this.sequenceNumber, "key attribute": getEnumMemberName( @@ -350,7 +346,7 @@ export class CentralSceneCCNotification extends CentralSceneCC { message["slow refresh"] = this.slowRefresh; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -359,16 +355,13 @@ export class CentralSceneCCNotification extends CentralSceneCC { @CCCommand(CentralSceneCommand.SupportedReport) export class CentralSceneCCSupportedReport extends CentralSceneCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.sceneCount = this.payload[0]; - if (this.version >= 3) { - this.supportsSlowRefresh = !!(this.payload[1] & 0b1000_0000); - } + this.supportsSlowRefresh = !!(this.payload[1] & 0b1000_0000); const bitMaskBytes = (this.payload[1] & 0b110) >>> 1; const identicalKeyAttributes = !!(this.payload[1] & 0b1); const numEntries = identicalKeyAttributes ? 1 : this.sceneCount; @@ -395,13 +388,13 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Create/extend metadata for all scenes for (let i = 1; i <= this.sceneCount; i++) { const sceneValue = CentralSceneCCValues.scene(i); - this.setMetadata(applHost, sceneValue, { + this.setMetadata(ctx, sceneValue, { ...sceneValue.meta, states: enumValuesToMetadataStates( CentralSceneKeys, @@ -433,7 +426,7 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { return this._supportedKeyAttributes; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "scene count": this.sceneCount, "supports slow refresh": maybeUnknownToString( @@ -446,7 +439,7 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -459,10 +452,9 @@ export class CentralSceneCCSupportedGet extends CentralSceneCC {} @CCCommand(CentralSceneCommand.ConfigurationReport) export class CentralSceneCCConfigurationReport extends CentralSceneCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.slowRefresh = !!(this.payload[0] & 0b1000_0000); @@ -471,9 +463,9 @@ export class CentralSceneCCConfigurationReport extends CentralSceneCC { @ccValue(CentralSceneCCValues.slowRefresh) public readonly slowRefresh: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "slow refresh": this.slowRefresh }, }; } @@ -494,12 +486,11 @@ export interface CentralSceneCCConfigurationSetOptions @useSupervision() export class CentralSceneCCConfigurationSet extends CentralSceneCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | CentralSceneCCConfigurationSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -512,14 +503,14 @@ export class CentralSceneCCConfigurationSet extends CentralSceneCC { public slowRefresh: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.slowRefresh ? 0b1000_0000 : 0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "slow refresh": this.slowRefresh }, }; } diff --git a/packages/cc/src/cc/ClimateControlScheduleCC.ts b/packages/cc/src/cc/ClimateControlScheduleCC.ts index e7c4d610276c..d02d8fd8ab95 100644 --- a/packages/cc/src/cc/ClimateControlScheduleCC.ts +++ b/packages/cc/src/cc/ClimateControlScheduleCC.ts @@ -9,7 +9,7 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -110,13 +110,13 @@ export class ClimateControlScheduleCCAPI extends CCAPI { ClimateControlScheduleCommand.Set, ); - const cc = new ClimateControlScheduleCCSet(this.applHost, { + const cc = new ClimateControlScheduleCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, weekday, switchPoints, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -128,12 +128,12 @@ export class ClimateControlScheduleCCAPI extends CCAPI { ClimateControlScheduleCommand.Get, ); - const cc = new ClimateControlScheduleCCGet(this.applHost, { + const cc = new ClimateControlScheduleCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, weekday, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ClimateControlScheduleCCReport >( cc, @@ -148,11 +148,11 @@ export class ClimateControlScheduleCCAPI extends CCAPI { ClimateControlScheduleCommand.ChangedGet, ); - const cc = new ClimateControlScheduleCCChangedGet(this.applHost, { + const cc = new ClimateControlScheduleCCChangedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ClimateControlScheduleCCChangedReport >( cc, @@ -168,11 +168,11 @@ export class ClimateControlScheduleCCAPI extends CCAPI { ClimateControlScheduleCommand.OverrideGet, ); - const cc = new ClimateControlScheduleCCOverrideGet(this.applHost, { + const cc = new ClimateControlScheduleCCOverrideGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ClimateControlScheduleCCOverrideReport >( cc, @@ -196,13 +196,13 @@ export class ClimateControlScheduleCCAPI extends CCAPI { ClimateControlScheduleCommand.OverrideSet, ); - const cc = new ClimateControlScheduleCCOverrideSet(this.applHost, { + const cc = new ClimateControlScheduleCCOverrideSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, overrideType: type, overrideState: state, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -223,12 +223,11 @@ export interface ClimateControlScheduleCCSetOptions extends CCCommandOptions { @useSupervision() export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ClimateControlScheduleCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -243,7 +242,7 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { public switchPoints: Switchpoint[]; public weekday: Weekday; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Make sure we have exactly 9 entries const allSwitchPoints = this.switchPoints.slice(0, 9); // maximum 9 while (allSwitchPoints.length < 9) { @@ -257,12 +256,12 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { Buffer.from([this.weekday & 0b111]), ...allSwitchPoints.map((sp) => encodeSwitchpoint(sp)), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { weekday: getEnumMemberName(Weekday, this.weekday), switchpoints: `${ @@ -287,10 +286,9 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { @CCCommand(ClimateControlScheduleCommand.Report) export class ClimateControlScheduleCCReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 28); // 1 + 9 * 3 this.weekday = this.payload[0] & 0b111; @@ -311,9 +309,9 @@ export class ClimateControlScheduleCCReport extends ClimateControlScheduleCC { ) public readonly schedule: readonly Switchpoint[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { weekday: getEnumMemberName(Weekday, this.weekday), schedule: `${ @@ -344,12 +342,11 @@ export interface ClimateControlScheduleCCGetOptions extends CCCommandOptions { @expectedCCResponse(ClimateControlScheduleCCReport) export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ClimateControlScheduleCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -362,14 +359,14 @@ export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { public weekday: Weekday; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.weekday & 0b111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { weekday: getEnumMemberName(Weekday, this.weekday) }, }; } @@ -380,10 +377,9 @@ export class ClimateControlScheduleCCChangedReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.changeCounter = this.payload[0]; @@ -391,9 +387,9 @@ export class ClimateControlScheduleCCChangedReport public readonly changeCounter: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "change counter": this.changeCounter }, }; } @@ -410,10 +406,9 @@ export class ClimateControlScheduleCCOverrideReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.overrideType = this.payload[0] & 0b11; @@ -427,9 +422,9 @@ export class ClimateControlScheduleCCOverrideReport @ccValue(ClimateControlScheduleCCValues.overrideState) public readonly overrideState: SetbackState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "override type": getEnumMemberName( ScheduleOverrideType, @@ -461,12 +456,11 @@ export class ClimateControlScheduleCCOverrideSet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ClimateControlScheduleCCOverrideSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -481,17 +475,17 @@ export class ClimateControlScheduleCCOverrideSet public overrideType: ScheduleOverrideType; public overrideState: SetbackState; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.overrideType & 0b11, encodeSetbackState(this.overrideState), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "override type": getEnumMemberName( ScheduleOverrideType, diff --git a/packages/cc/src/cc/ClockCC.ts b/packages/cc/src/cc/ClockCC.ts index 3c1ffcf09a4f..524422030d9d 100644 --- a/packages/cc/src/cc/ClockCC.ts +++ b/packages/cc/src/cc/ClockCC.ts @@ -10,11 +10,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -23,6 +19,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -53,11 +51,11 @@ export class ClockCCAPI extends CCAPI { public async get() { this.assertSupportsCommand(ClockCommand, ClockCommand.Get); - const cc = new ClockCCGet(this.applHost, { + const cc = new ClockCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -74,14 +72,14 @@ export class ClockCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(ClockCommand, ClockCommand.Set); - const cc = new ClockCCSet(this.applHost, { + const cc = new ClockCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, hour, minute, weekday: weekday ?? Weekday.Unknown, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -90,33 +88,37 @@ export class ClockCCAPI extends CCAPI { export class ClockCC extends CommandClass { declare ccCommand: ClockCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Clock, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "requesting current clock setting...", direction: "outbound", }); @@ -129,7 +131,7 @@ export class ClockCC extends CommandClass { }${response.hour < 10 ? "0" : ""}${response.hour}:${ response.minute < 10 ? "0" : "" }${response.minute}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -148,10 +150,9 @@ export interface ClockCCSetOptions extends CCCommandOptions { @useSupervision() export class ClockCCSet extends ClockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ClockCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -169,17 +170,17 @@ export class ClockCCSet extends ClockCC { public hour: number; public minute: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ ((this.weekday & 0b111) << 5) | (this.hour & 0b11111), this.minute, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "clock setting": `${ getEnumMemberName( @@ -201,10 +202,9 @@ export class ClockCCSet extends ClockCC { @CCCommand(ClockCommand.Report) export class ClockCCReport extends ClockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.weekday = this.payload[0] >>> 5; @@ -221,9 +221,9 @@ export class ClockCCReport extends ClockCC { public readonly hour: number; public readonly minute: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "clock setting": `${ getEnumMemberName( diff --git a/packages/cc/src/cc/ColorSwitchCC.ts b/packages/cc/src/cc/ColorSwitchCC.ts index 9d215ea13e3f..0b163ece62c4 100644 --- a/packages/cc/src/cc/ColorSwitchCC.ts +++ b/packages/cc/src/cc/ColorSwitchCC.ts @@ -16,11 +16,7 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown, encodeBitMask } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { type AllOrNone, getEnumMemberName, @@ -46,6 +42,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -207,11 +207,11 @@ export class ColorSwitchCCAPI extends CCAPI { ColorSwitchCommand.SupportedGet, ); - const cc = new ColorSwitchCCSupportedGet(this.applHost, { + const cc = new ColorSwitchCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ColorSwitchCCSupportedReport >( cc, @@ -225,12 +225,12 @@ export class ColorSwitchCCAPI extends CCAPI { public async get(component: ColorComponent) { this.assertSupportsCommand(ColorSwitchCommand, ColorSwitchCommand.Get); - const cc = new ColorSwitchCCGet(this.applHost, { + const cc = new ColorSwitchCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, colorComponent: component, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -245,13 +245,13 @@ export class ColorSwitchCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(ColorSwitchCommand, ColorSwitchCommand.Set); - const cc = new ColorSwitchCCSet(this.applHost, { + const cc = new ColorSwitchCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - const result = await this.applHost.sendCommand(cc, this.commandOptions); + const result = await this.host.sendCommand(cc, this.commandOptions); if (isUnsupervisedOrSucceeded(result)) { // If the command did not fail, assume that it succeeded and update the values accordingly @@ -270,7 +270,7 @@ export class ColorSwitchCCAPI extends CCAPI { ); // and optimistically update the currentColor for (const node of affectedNodes) { - const valueDB = this.applHost.tryGetValueDB(node.id); + const valueDB = this.host.tryGetValueDB(node.id); if (valueDB) { this.updateCurrentColor(valueDB, cc.colorTable); } @@ -363,13 +363,13 @@ export class ColorSwitchCCAPI extends CCAPI { ColorSwitchCommand.StartLevelChange, ); - const cc = new ColorSwitchCCStartLevelChange(this.applHost, { + const cc = new ColorSwitchCCStartLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -381,13 +381,13 @@ export class ColorSwitchCCAPI extends CCAPI { ColorSwitchCommand.StopLevelChange, ); - const cc = new ColorSwitchCCStopLevelChange(this.applHost, { + const cc = new ColorSwitchCCStopLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, colorComponent, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -543,31 +543,33 @@ export class ColorSwitchCCAPI extends CCAPI { export class ColorSwitchCC extends CommandClass { declare ccCommand: ColorSwitchCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Color Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported colors...", direction: "outbound", }); const supportedColors = await api.getSupported(); if (!supportedColors) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported colors timed out, skipping interview...", @@ -576,7 +578,7 @@ export class ColorSwitchCC extends CommandClass { return; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received supported colors:${ supportedColors @@ -590,17 +592,17 @@ export class ColorSwitchCC extends CommandClass { for (const color of supportedColors) { const currentColorChannelValue = ColorSwitchCCValues .currentColorChannel(color); - this.setMetadata(applHost, currentColorChannelValue); + this.setMetadata(ctx, currentColorChannelValue); const targetColorChannelValue = ColorSwitchCCValues .targetColorChannel(color); - this.setMetadata(applHost, targetColorChannelValue); + this.setMetadata(ctx, targetColorChannelValue); } // And the compound one const currentColorValue = ColorSwitchCCValues.currentColor; - this.setMetadata(applHost, currentColorValue); + this.setMetadata(ctx, currentColorValue); const targetColorValue = ColorSwitchCCValues.targetColor; - this.setMetadata(applHost, targetColorValue); + this.setMetadata(ctx, targetColorValue); // Create the collective HEX color values const supportsHex = [ @@ -609,35 +611,37 @@ export class ColorSwitchCC extends CommandClass { ColorComponent.Blue, ].every((c) => supportedColors.includes(c)); this.setValue( - applHost, + ctx, ColorSwitchCCValues.supportsHexColor, supportsHex, ); if (supportsHex) { const hexColorValue = ColorSwitchCCValues.hexColor; - this.setMetadata(applHost, hexColorValue); + this.setMetadata(ctx, hexColorValue); } // Query all color components - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Color Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const supportedColors: readonly ColorComponent[] = this.getValue( - applHost, + ctx, ColorSwitchCCValues.supportedColorComponents, ) ?? []; @@ -647,7 +651,7 @@ export class ColorSwitchCC extends CommandClass { if (!isEnumMember(ColorComponent, color)) continue; const colorName = getEnumMemberName(ColorComponent, color); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current color state (${colorName})`, direction: "outbound", @@ -657,7 +661,7 @@ export class ColorSwitchCC extends CommandClass { } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -668,7 +672,7 @@ export class ColorSwitchCC extends CommandClass { const translated = ColorComponent[propertyKey]; if (translated) return translated; } - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } @@ -680,12 +684,11 @@ export interface ColorSwitchCCSupportedReportOptions { @CCCommand(ColorSwitchCommand.SupportedReport) export class ColorSwitchCCSupportedReport extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (ColorSwitchCCSupportedReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Docs say 'variable length', but the table shows 2 bytes. @@ -703,18 +706,18 @@ export class ColorSwitchCCSupportedReport extends ColorSwitchCC { @ccValue(ColorSwitchCCValues.supportedColorComponents) public readonly supportedColorComponents: readonly ColorComponent[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeBitMask( this.supportedColorComponents, 15, // fixed 2 bytes ColorComponent["Warm White"], ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported color components": this.supportedColorComponents .map((c) => `\n· ${getEnumMemberName(ColorComponent, c)}`) @@ -742,19 +745,18 @@ export type ColorSwitchCCReportOptions = @CCCommand(ColorSwitchCommand.Report) export class ColorSwitchCCReport extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (ColorSwitchCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.colorComponent = this.payload[0]; this.currentValue = this.payload[1]; - if (this.version >= 3 && this.payload.length >= 4) { + if (this.payload.length >= 4) { this.targetValue = this.payload[2]; this.duration = Duration.parseReport(this.payload[3]); } @@ -766,19 +768,19 @@ export class ColorSwitchCCReport extends ColorSwitchCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { + public persistValues(ctx: PersistValuesContext): boolean { // Duration is stored globally instead of per component - if (!super.persistValues(applHost)) return false; + if (!super.persistValues(ctx)) return false; // Update compound current value const colorTableKey = colorComponentToTableKey(this.colorComponent); if (colorTableKey) { const compoundCurrentColorValue = ColorSwitchCCValues.currentColor; const compoundCurrentColor: Partial> = - this.getValue(applHost, compoundCurrentColorValue) ?? {}; + this.getValue(ctx, compoundCurrentColorValue) ?? {}; compoundCurrentColor[colorTableKey] = this.currentValue; this.setValue( - applHost, + ctx, compoundCurrentColorValue, compoundCurrentColor, ); @@ -788,10 +790,10 @@ export class ColorSwitchCCReport extends ColorSwitchCC { const compoundTargetColorValue = ColorSwitchCCValues.targetColor; const compoundTargetColor: Partial> = - this.getValue(applHost, compoundTargetColorValue) ?? {}; + this.getValue(ctx, compoundTargetColorValue) ?? {}; compoundTargetColor[colorTableKey] = this.targetValue; this.setValue( - applHost, + ctx, compoundTargetColorValue, compoundTargetColor, ); @@ -800,7 +802,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { // Update collective hex value if required const supportsHex = !!this.getValue( - applHost, + ctx, ColorSwitchCCValues.supportsHexColor, ); if ( @@ -811,7 +813,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { ) { const hexColorValue = ColorSwitchCCValues.hexColor; - const hexValue: string = this.getValue(applHost, hexColorValue) + const hexValue: string = this.getValue(ctx, hexColorValue) ?? "000000"; const byteOffset = ColorComponent.Blue - this.colorComponent; const byteMask = 0xff << (byteOffset * 8); @@ -819,7 +821,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { hexValueNumeric = (hexValueNumeric & ~byteMask) | (this.currentValue << (byteOffset * 8)); this.setValue( - applHost, + ctx, hexColorValue, hexValueNumeric.toString(16).padStart(6, "0"), ); @@ -844,7 +846,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { @ccValue(ColorSwitchCCValues.duration) public readonly duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.colorComponent, this.currentValue, @@ -858,10 +860,10 @@ export class ColorSwitchCCReport extends ColorSwitchCC { ]), ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "color component": getEnumMemberName( ColorComponent, @@ -876,7 +878,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -898,10 +900,9 @@ function testResponseForColorSwitchGet( @expectedCCResponse(ColorSwitchCCReport, testResponseForColorSwitchGet) export class ColorSwitchCCGet extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ColorSwitchCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this._colorComponent = this.payload[0]; @@ -924,14 +925,14 @@ export class ColorSwitchCCGet extends ColorSwitchCC { this._colorComponent = value; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this._colorComponent]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "color component": getEnumMemberName( ColorComponent, @@ -951,12 +952,11 @@ export type ColorSwitchCCSetOptions = (ColorTable | { hexColor: string }) & { @useSupervision() export class ColorSwitchCCSet extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & ColorSwitchCCSetOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const populatedColorCount = this.payload[0] & 0b11111; @@ -1000,7 +1000,7 @@ export class ColorSwitchCCSet extends ColorSwitchCC { public colorTable: ColorTable; public duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const populatedColorCount = Object.keys(this.colorTable).length; this.payload = Buffer.allocUnsafe( 1 + populatedColorCount * 2 + 1, @@ -1017,8 +1017,9 @@ export class ColorSwitchCCSet extends ColorSwitchCC { this.duration ?? Duration.default() ).serializeSet(); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -1026,10 +1027,10 @@ export class ColorSwitchCCSet extends ColorSwitchCC { this.payload = this.payload.subarray(0, -1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; for (const [key, value] of Object.entries(this.colorTable)) { const realKey: string = key in ColorComponentMap @@ -1041,7 +1042,7 @@ export class ColorSwitchCCSet extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1072,12 +1073,11 @@ export type ColorSwitchCCStartLevelChangeOptions = @useSupervision() export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & ColorSwitchCCStartLevelChangeOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5; @@ -1106,7 +1106,7 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { public direction: keyof typeof LevelChangeDirection; public colorComponent: ColorComponent; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const controlByte = (LevelChangeDirection[this.direction] << 6) | (this.ignoreStartLevel ? 0b0010_0000 : 0); this.payload = Buffer.from([ @@ -1116,8 +1116,9 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { (this.duration ?? Duration.default()).serializeSet(), ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 3 && this.host.getDeviceConfig?.( + ccVersion < 3 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -1125,10 +1126,10 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { this.payload = this.payload.subarray(0, -1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "color component": getEnumMemberName( ColorComponent, @@ -1143,7 +1144,7 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1158,12 +1159,11 @@ export interface ColorSwitchCCStopLevelChangeOptions extends CCCommandOptions { @useSupervision() export class ColorSwitchCCStopLevelChange extends ColorSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ColorSwitchCCStopLevelChangeOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.colorComponent = this.payload[0]; @@ -1174,14 +1174,14 @@ export class ColorSwitchCCStopLevelChange extends ColorSwitchCC { public readonly colorComponent: ColorComponent; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.colorComponent]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "color component": getEnumMemberName( ColorComponent, diff --git a/packages/cc/src/cc/ConfigurationCC.ts b/packages/cc/src/cc/ConfigurationCC.ts index 851850340327..422253fabd89 100644 --- a/packages/cc/src/cc/ConfigurationCC.ts +++ b/packages/cc/src/cc/ConfigurationCC.ts @@ -3,14 +3,17 @@ import { CommandClasses, ConfigValueFormat, type ConfigurationMetadata, - type IVirtualEndpoint, - type IZWaveEndpoint, + type ControlsCC, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, type SupervisionResult, SupervisionStatus, + type SupportsCC, type ValueID, ValueMetadata, ZWaveError, @@ -27,9 +30,12 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -38,6 +44,7 @@ import { composeObject } from "alcalzone-shared/objects"; import { padStart } from "alcalzone-shared/strings"; import { CCAPI, + type CCAPIEndpoint, POLL_VALUE, type PollValueImplementation, SET_VALUE, @@ -50,6 +57,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -144,26 +155,24 @@ type NormalizedConfigurationCCAPISetOptions = ); function createConfigurationCCInstance( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + endpoint: CCAPIEndpoint, ): ConfigurationCC { return CommandClass.createInstanceUnchecked( - applHost, endpoint.virtual ? endpoint.node.physicalNodes[0] : endpoint, ConfigurationCC, )!; } function normalizeConfigurationCCAPISetOptions( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + ctx: GetValueDB, + endpoint: CCAPIEndpoint, options: ConfigurationCCAPISetOptions, ): NormalizedConfigurationCCAPISetOptions { if ("bitMask" in options && options.bitMask) { // Variant 3: Partial param, look it up in the device config - const ccc = createConfigurationCCInstance(applHost, endpoint); + const ccc = createConfigurationCCInstance(endpoint); const paramInfo = ccc.getParamInformation( - applHost, + ctx, options.parameter, options.bitMask, ); @@ -190,9 +199,9 @@ function normalizeConfigurationCCAPISetOptions( ]); } else { // Variant 1: Normal parameter, defined in a config file - const ccc = createConfigurationCCInstance(applHost, endpoint); + const ccc = createConfigurationCCInstance(endpoint); const paramInfo = ccc.getParamInformation( - applHost, + ctx, options.parameter, options.bitMask, ); @@ -212,8 +221,8 @@ function normalizeConfigurationCCAPISetOptions( } function bulkMergePartialParamValues( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + ctx: GetValueDB, + endpoint: CCAPIEndpoint, options: NormalizedConfigurationCCAPISetOptions[], ): (NormalizedConfigurationCCAPISetOptions & { bitMask?: undefined })[] { // Merge partial parameters before doing anything else. Therefore, take the non-partials, ... @@ -231,12 +240,12 @@ function bulkMergePartialParamValues( } // and push the merged result into the array we'll be working with if (unmergedPartials.size) { - const ccc = createConfigurationCCInstance(applHost, endpoint); + const ccc = createConfigurationCCInstance(endpoint); for (const [parameter, partials] of unmergedPartials) { allParams.push({ parameter, value: ccc.composePartialParamValues( - applHost, + ctx, parameter, partials.map((p) => ({ bitMask: p.bitMask!, @@ -277,11 +286,11 @@ function reInterpretSignedValue( } function getParamInformationFromConfigFile( - applHost: ZWaveApplicationHost, + ctx: GetDeviceConfig, nodeId: number, endpointIndex: number, ): ParamInfoMap | undefined { - const deviceConfig = applHost.getDeviceConfig?.(nodeId); + const deviceConfig = ctx.getDeviceConfig?.(nodeId); if (endpointIndex === 0) { return ( deviceConfig?.paramInformation @@ -338,13 +347,10 @@ export class ConfigurationCCAPI extends CCAPI { } let ccInstance: ConfigurationCC; - const applHost = this.applHost; + const applHost = this.host; if (this.isSinglecast()) { - ccInstance = createConfigurationCCInstance( - this.applHost, - this.endpoint, - ); + ccInstance = createConfigurationCCInstance(this.endpoint); } else if (this.isMulticast()) { // Multicast is only possible if the parameter definition is the same on all target nodes const nodes = this.endpoint.node.physicalNodes; @@ -364,10 +370,9 @@ export class ConfigurationCCAPI extends CCAPI { const paramInfos = this.endpoint.node.physicalNodes.map( (node) => createConfigurationCCInstance( - this.applHost, node.getEndpoint(this.endpoint.index)!, ).getParamInformation( - this.applHost, + this.host, property, propertyKey, ), @@ -388,10 +393,7 @@ export class ConfigurationCCAPI extends CCAPI { ); } // If it is, just use the first node to create the CC instance - ccInstance = createConfigurationCCInstance( - this.applHost, - this.endpoint, - ); + ccInstance = createConfigurationCCInstance(this.endpoint); } else { throw new ZWaveError( `The setValue API for Configuration CC is not supported via broadcast!`, @@ -530,13 +532,13 @@ export class ConfigurationCCAPI extends CCAPI { const { valueBitMask, allowUnexpectedResponse } = options ?? {}; - const cc = new ConfigurationCCGet(this.applHost, { + const cc = new ConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, allowUnexpectedResponse, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -547,7 +549,7 @@ export class ConfigurationCCAPI extends CCAPI { if (!valueBitMask) return response.value; // If a partial parameter was requested, extract that value const paramInfo = cc.getParamInformation( - this.applHost, + this.host, response.parameter, valueBitMask, ); @@ -557,7 +559,7 @@ export class ConfigurationCCAPI extends CCAPI { isSignedPartial(valueBitMask, paramInfo.format), ); } - this.applHost.controllerLog.logNode(this.endpoint.nodeId, { + this.host.logNode(this.endpoint.nodeId, { endpoint: this.endpoint.index, message: `Received unexpected ConfigurationReport (param = ${response.parameter}, value = ${response.value.toString()})`, @@ -599,12 +601,12 @@ export class ConfigurationCCAPI extends CCAPI { this.supportsCommand(ConfigurationCommand.BulkGet) && isConsecutiveArray(distinctParameters) ) { - const cc = new ConfigurationCCBulkGet(this.applHost, { + const cc = new ConfigurationCCBulkGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameters: distinctParameters, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCBulkReport >( cc, @@ -619,12 +621,12 @@ export class ConfigurationCCAPI extends CCAPI { const _values = new Map(); for (const parameter of distinctParameters) { - const cc = new ConfigurationCCGet(this.applHost, { + const cc = new ConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCReport >( cc, @@ -638,12 +640,12 @@ export class ConfigurationCCAPI extends CCAPI { } // Combine the returned values with the requested ones - const cc = createConfigurationCCInstance(this.applHost, this.endpoint); + const cc = createConfigurationCCInstance(this.endpoint); return options.map((o) => { let value = values?.get(o.parameter); if (typeof value === "number" && o.bitMask) { const paramInfo = cc.getParamInformation( - this.applHost, + this.host, o.parameter, o.bitMask, ); @@ -675,24 +677,21 @@ export class ConfigurationCCAPI extends CCAPI { ); const normalized = normalizeConfigurationCCAPISetOptions( - this.applHost, + this.host, this.endpoint, options, ); let value = normalized.value; if (normalized.bitMask) { - const ccc = createConfigurationCCInstance( - this.applHost, - this.endpoint, - ); + const ccc = createConfigurationCCInstance(this.endpoint); value = ccc.composePartialParamValue( - this.applHost, + this.host, normalized.parameter, normalized.bitMask, normalized.value, ); } - const cc = new ConfigurationCCSet(this.applHost, { + const cc = new ConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, resetToDefault: false, @@ -702,7 +701,7 @@ export class ConfigurationCCAPI extends CCAPI { valueFormat: normalized.valueFormat, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -715,14 +714,14 @@ export class ConfigurationCCAPI extends CCAPI { // Normalize the values so we can better work with them const normalized = values.map((v) => normalizeConfigurationCCAPISetOptions( - this.applHost, + this.host, this.endpoint, v, ) ); // And merge multiple partials that belong the same "full" value const allParams = bulkMergePartialParamValues( - this.applHost, + this.host, this.endpoint, normalized, ); @@ -736,7 +735,7 @@ export class ConfigurationCCAPI extends CCAPI { && new Set(allParams.map((v) => v.valueSize)).size === 1; if (canUseBulkSet) { - const cc = new ConfigurationCCBulkSet(this.applHost, { + const cc = new ConfigurationCCBulkSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameters: allParams.map((v) => v.parameter), @@ -746,7 +745,7 @@ export class ConfigurationCCAPI extends CCAPI { handshake: true, }); // The handshake flag is set, so we expect a BulkReport in response - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ConfigurationCCBulkReport >( cc, @@ -784,7 +783,7 @@ export class ConfigurationCCAPI extends CCAPI { valueFormat, } of allParams ) { - const cc = new ConfigurationCCSet(this.applHost, { + const cc = new ConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, @@ -793,7 +792,7 @@ export class ConfigurationCCAPI extends CCAPI { valueFormat, }); supervisionResults.push( - await this.applHost.sendCommand(cc, this.commandOptions), + await this.host.sendCommand(cc, this.commandOptions), ); } return mergeSupervisionResults(supervisionResults); @@ -814,18 +813,29 @@ export class ConfigurationCCAPI extends CCAPI { return this.resetBulk([parameter]); } + // According to SDS14223 this flag SHOULD NOT be set + // Because we don't want to test the behavior, we enforce that it MUST not be set + // on legacy nodes + if (this.version <= 3) { + throw new ZWaveError( + `Resetting configuration parameters to default MUST not be done on nodes implementing ConfigurationCC V3 or below!`, + ZWaveErrorCodes + .ConfigurationCC_NoResetToDefaultOnLegacyDevices, + ); + } + this.assertSupportsCommand( ConfigurationCommand, ConfigurationCommand.Set, ); - const cc = new ConfigurationCCSet(this.applHost, { + const cc = new ConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, resetToDefault: true, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -841,13 +851,13 @@ export class ConfigurationCCAPI extends CCAPI { isConsecutiveArray(parameters) && this.supportsCommand(ConfigurationCommand.BulkSet) ) { - const cc = new ConfigurationCCBulkSet(this.applHost, { + const cc = new ConfigurationCCBulkSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameters, resetToDefault: true, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } else { this.assertSupportsCommand( ConfigurationCommand, @@ -855,7 +865,7 @@ export class ConfigurationCCAPI extends CCAPI { ); const CCs = distinct(parameters).map( (parameter) => - new ConfigurationCCSet(this.applHost, { + new ConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, @@ -863,7 +873,7 @@ export class ConfigurationCCAPI extends CCAPI { }), ); for (const cc of CCs) { - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } } @@ -878,11 +888,11 @@ export class ConfigurationCCAPI extends CCAPI { ConfigurationCommand.DefaultReset, ); - const cc = new ConfigurationCCDefaultReset(this.applHost, { + const cc = new ConfigurationCCDefaultReset({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -891,12 +901,12 @@ export class ConfigurationCCAPI extends CCAPI { // Get-type commands are only possible in singlecast this.assertPhysicalEndpoint(this.endpoint); - const cc = new ConfigurationCCPropertiesGet(this.applHost, { + const cc = new ConfigurationCCPropertiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCPropertiesReport >( cc, @@ -924,12 +934,12 @@ export class ConfigurationCCAPI extends CCAPI { // Get-type commands are only possible in singlecast this.assertPhysicalEndpoint(this.endpoint); - const cc = new ConfigurationCCNameGet(this.applHost, { + const cc = new ConfigurationCCNameGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCNameReport >( cc, @@ -944,12 +954,12 @@ export class ConfigurationCCAPI extends CCAPI { // Get-type commands are only possible in singlecast this.assertPhysicalEndpoint(this.endpoint); - const cc = new ConfigurationCCInfoGet(this.applHost, { + const cc = new ConfigurationCCInfoGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCInfoReport >( cc, @@ -981,18 +991,15 @@ export class ConfigurationCCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); // TODO: Reduce the priority of the messages - this.applHost.controllerLog.logNode(this.endpoint.nodeId, { + this.host.logNode(this.endpoint.nodeId, { endpoint: this.endpoint.index, message: `Scanning available parameters...`, }); - const ccInstance = createConfigurationCCInstance( - this.applHost, - this.endpoint, - ); + const ccInstance = createConfigurationCCInstance(this.endpoint); for (let param = 1; param <= 255; param++) { // Check if the parameter is readable let originalValue: ConfigValue | undefined; - this.applHost.controllerLog.logNode(this.endpoint.nodeId, { + this.host.logNode(this.endpoint.nodeId, { endpoint: this.endpoint.index, message: ` trying param ${param}...`, direction: "outbound", @@ -1008,11 +1015,11 @@ export class ConfigurationCCAPI extends CCAPI { const logMessage = ` Param ${param}: readable = true valueSize = ${ - ccInstance.getParamInformation(this.applHost, param) + ccInstance.getParamInformation(this.host, param) .valueSize } value = ${originalValue.toString()}`; - this.applHost.controllerLog.logNode(this.endpoint.nodeId, { + this.host.logNode(this.endpoint.nodeId, { endpoint: this.endpoint.index, message: logMessage, direction: "inbound", @@ -1040,44 +1047,46 @@ export class ConfigurationCCAPI extends CCAPI { export class ConfigurationCC extends CommandClass { declare ccCommand: ConfigurationCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Configuration, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - const deviceConfig = applHost.getDeviceConfig?.(node.id); + const deviceConfig = ctx.getDeviceConfig?.(node.id); const paramInfo = getParamInformationFromConfigFile( - applHost, + ctx, node.id, this.endpointIndex, ); if (paramInfo) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `${this.constructor.name}: Loading configuration parameters from device config`, direction: "none", }); - this.deserializeParamInformationFromConfig(applHost, paramInfo); + this.deserializeParamInformationFromConfig(ctx, paramInfo); } const documentedParamNumbers = new Set( Array.from(paramInfo?.keys() ?? []).map((k) => k.parameter), ); - if (this.version >= 3) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 3) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "finding first configuration parameter...", direction: "outbound", @@ -1087,7 +1096,7 @@ export class ConfigurationCC extends CommandClass { if (param0props) { param = param0props.nextParameter; if (param === 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `didn't report any config params, trying #1 just to be sure...`, @@ -1096,7 +1105,7 @@ export class ConfigurationCC extends CommandClass { param = 1; } } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Finding first configuration parameter timed out, skipping interview...", @@ -1106,7 +1115,7 @@ export class ConfigurationCC extends CommandClass { } while (param > 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying parameter #${param} information...`, direction: "outbound", @@ -1118,7 +1127,7 @@ export class ConfigurationCC extends CommandClass { () => undefined, ); if (!props) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying parameter #${param} information timed out, skipping scan...`, @@ -1175,7 +1184,7 @@ is advanced (UI): ${!!properties.isAdvanced} has bulk support: ${!properties.noBulkSupport} alters capabilities: ${!!properties.altersCapabilities}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -1192,27 +1201,29 @@ alters capabilities: ${!!properties.altersCapabilities}`; } } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Configuration, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - if (this.version < 3) { + if (api.version < 3) { // V1/V2: Query all values defined in the config file const paramInfo = getParamInformationFromConfigFile( - applHost, + ctx, node.id, this.endpointIndex, ); @@ -1228,7 +1239,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; alreadyQueried.add(param.parameter); // Query the current value - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying parameter #${param.parameter} value...`, @@ -1237,14 +1248,14 @@ alters capabilities: ${!!properties.altersCapabilities}`; // ... at least try to const paramValue = await api.get(param.parameter); if (typeof paramValue === "number") { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `parameter #${param.parameter} has value: ${paramValue}`, direction: "inbound", }); } else if (!paramValue) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received no value for parameter #${param.parameter}`, @@ -1254,7 +1265,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; } } } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `${this.constructor.name}: skipping interview because CC version is < 3 and there is no config file`, @@ -1264,22 +1275,22 @@ alters capabilities: ${!!properties.altersCapabilities}`; } else { // V3+: Query the values of discovered parameters const parameters = distinct( - this.getDefinedValueIDs(applHost) + this.getDefinedValueIDs(ctx) .map((v) => v.property) .filter((p) => typeof p === "number"), ); for (const param of parameters) { if ( - this.getParamInformation(applHost, param).readable !== false + this.getParamInformation(ctx, param).readable !== false ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying parameter #${param} value...`, direction: "outbound", }); await api.get(param); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `not querying parameter #${param} value, because it is writeonly`, @@ -1295,20 +1306,20 @@ alters capabilities: ${!!properties.altersCapabilities}`; * If this is true, we don't trust what the node reports */ protected paramExistsInConfigFile( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetDeviceConfig, parameter: number, valueBitMask?: number, ): boolean { if ( this.getValue( - applHost, + ctx, ConfigurationCCValues.isParamInformationFromConfig, ) !== true ) { return false; } const paramInformation = getParamInformationFromConfigFile( - applHost, + ctx, this.nodeId as number, this.endpointIndex, ); @@ -1332,7 +1343,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; * Stores config parameter metadata for this CC's node */ public extendParamInformation( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetDeviceConfig, parameter: number, valueBitMask: number | undefined, info: Partial, @@ -1340,14 +1351,14 @@ alters capabilities: ${!!properties.altersCapabilities}`; // Don't trust param information that a node reports if we have already loaded it from a config file if ( valueBitMask === undefined - && this.paramExistsInConfigFile(applHost, parameter) + && this.paramExistsInConfigFile(ctx, parameter) ) { return; } // Retrieve the base metadata const metadata = this.getParamInformation( - applHost, + ctx, parameter, valueBitMask, ); @@ -1355,7 +1366,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; Object.assign(metadata, info); // And store it back this.setMetadata( - applHost, + ctx, ConfigurationCCValues.paramInformation(parameter, valueBitMask), metadata, ); @@ -1366,13 +1377,13 @@ alters capabilities: ${!!properties.altersCapabilities}`; * Returns stored config parameter metadata for this CC's node */ public getParamInformation( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, parameter: number, valueBitMask?: number, ): ConfigurationMetadata { return ( this.getMetadata( - applHost, + ctx, ConfigurationCCValues.paramInformation(parameter, valueBitMask), ) ?? { ...ValueMetadata.Any, @@ -1385,17 +1396,23 @@ alters capabilities: ${!!properties.altersCapabilities}`; * and does not include partial parameters. */ public getQueriedParamInfos( - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): Record { const parameters = distinct( - this.getDefinedValueIDs(applHost) + this.getDefinedValueIDs(ctx) .map((v) => v.property) .filter((p) => typeof p === "number"), ); return composeObject( parameters.map((p) => [ p as any, - this.getParamInformation(applHost, p), + this.getParamInformation(ctx, p), ]), ); } @@ -1404,10 +1421,10 @@ alters capabilities: ${!!properties.altersCapabilities}`; * Returns stored config parameter metadata for all partial config params addressed with the given parameter number */ public getPartialParamInfos( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, parameter: number, ): (ValueID & { metadata: ConfigurationMetadata })[] { - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); return valueDB.findMetadata( (id) => id.commandClass === this.ccId @@ -1421,12 +1438,12 @@ alters capabilities: ${!!properties.altersCapabilities}`; * Computes the full value of a parameter after applying a partial param value */ public composePartialParamValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, parameter: number, bitMask: number, partialValue: number, ): number { - return this.composePartialParamValues(applHost, parameter, [ + return this.composePartialParamValues(ctx, parameter, [ { bitMask, partialValue }, ]); } @@ -1435,14 +1452,14 @@ alters capabilities: ${!!properties.altersCapabilities}`; * Computes the full value of a parameter after applying multiple partial param values */ public composePartialParamValues( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, parameter: number, partials: { bitMask: number; partialValue: number; }[], ): number { - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); // Add the other values together const otherValues = valueDB.findValues( (id) => @@ -1469,10 +1486,10 @@ alters capabilities: ${!!properties.altersCapabilities}`; /** Deserializes the config parameter info from a config file */ public deserializeParamInformationFromConfig( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetDeviceConfig, config: ParamInfoMap, ): void { - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); // Clear old param information for (const meta of valueDB.getAllMetadata(this.ccId)) { @@ -1492,7 +1509,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; // Allow overwriting the param info (mark it as unloaded) this.setValue( - applHost, + ctx, ConfigurationCCValues.isParamInformationFromConfig, false, ); @@ -1527,7 +1544,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; isFromConfig: true, }); this.extendParamInformation( - applHost, + ctx, param.parameter, param.valueBitMask, paramInfo, @@ -1536,14 +1553,14 @@ alters capabilities: ${!!properties.altersCapabilities}`; // Remember that we loaded the param information from a config file this.setValue( - applHost, + ctx, ConfigurationCCValues.isParamInformationFromConfig, true, ); } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey?: string | number, ): string | undefined { @@ -1555,11 +1572,11 @@ alters capabilities: ${!!properties.altersCapabilities}`; // so no name for the property key is required return undefined; } - return super.translateProperty(applHost, property, propertyKey); + return super.translateProperty(ctx, property, propertyKey); } public translateProperty( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey?: string | number, ): string { @@ -1569,7 +1586,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; && (propertyKey == undefined || typeof propertyKey === "number") ) { const paramInfo = this.getParamInformation( - applHost, + ctx, property, propertyKey, ); @@ -1581,7 +1598,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; } return ret; } - return super.translateProperty(applHost, property, propertyKey); + return super.translateProperty(ctx, property, propertyKey); } } @@ -1596,12 +1613,11 @@ export interface ConfigurationCCReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.Report) export class ConfigurationCCReport extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // All fields must be present @@ -1634,14 +1650,16 @@ export class ConfigurationCCReport extends ConfigurationCC { public valueSize: number; private valueFormat?: ConfigValueFormat; // only used for serialization - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + + const ccVersion = getEffectiveCCVersion(ctx, this); // This parameter may be a partial param in the following cases: // * a config file defines it as such // * it was reported by the device as a bit field const partialParams = this.getPartialParamInfos( - applHost, + ctx, this.parameter, ); @@ -1653,19 +1671,19 @@ export class ConfigurationCCReport extends ConfigurationCC { } else { // Check if the initial assumption of SignedInteger holds true const oldParamInformation = this.getParamInformation( - applHost, + ctx, this.parameter, ); cachedValueFormat = oldParamInformation.format; // On older CC versions, these reports may be the only way we can retrieve the value size // Therefore we store it here - this.extendParamInformation(applHost, this.parameter, undefined, { + this.extendParamInformation(ctx, this.parameter, undefined, { valueSize: this.valueSize, }); if ( - this.version < 3 - && !this.paramExistsInConfigFile(applHost, this.parameter) + ccVersion < 3 + && !this.paramExistsInConfigFile(ctx, this.parameter) && oldParamInformation.min == undefined && oldParamInformation.max == undefined ) { @@ -1673,7 +1691,7 @@ export class ConfigurationCCReport extends ConfigurationCC { || oldParamInformation.format === ConfigValueFormat.SignedInteger; this.extendParamInformation( - applHost, + ctx, this.parameter, undefined, getIntegerLimits(this.valueSize as any, isSigned), @@ -1700,7 +1718,7 @@ export class ConfigurationCCReport extends ConfigurationCC { for (const param of partialParams) { if (typeof param.propertyKey === "number") { this.setValue( - applHost, + ctx, ConfigurationCCValues.paramInformation( this.parameter, param.propertyKey, @@ -1719,7 +1737,7 @@ export class ConfigurationCCReport extends ConfigurationCC { } else { // This is a single param this.setValue( - applHost, + ctx, ConfigurationCCValues.paramInformation(this.parameter), this.value, ); @@ -1727,7 +1745,7 @@ export class ConfigurationCCReport extends ConfigurationCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.parameter, this.valueSize & 0b111]), Buffer.allocUnsafe(this.valueSize), @@ -1740,12 +1758,12 @@ export class ConfigurationCCReport extends ConfigurationCC { this.value, ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter, "value size": this.valueSize, @@ -1779,10 +1797,9 @@ export interface ConfigurationCCGetOptions extends CCCommandOptions { @expectedCCResponse(ConfigurationCCReport, testResponseForConfigurationGet) export class ConfigurationCCGet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.parameter = this.payload[0]; @@ -1797,14 +1814,14 @@ export class ConfigurationCCGet extends ConfigurationCC { public parameter: number; public allowUnexpectedResponse: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.parameter]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter }, }; } @@ -1832,10 +1849,9 @@ export type ConfigurationCCSetOptions = @useSupervision() export class ConfigurationCCSet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ConfigurationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.parameter = this.payload[0]; @@ -1856,16 +1872,6 @@ export class ConfigurationCCSet extends ConfigurationCC { ); } else { this.parameter = options.parameter; - // According to SDS14223 this flag SHOULD NOT be set - // Because we don't want to test the behavior, we enforce that it MUST not be set - // on legacy nodes - if (options.resetToDefault && this.version <= 3) { - throw new ZWaveError( - `The resetToDefault flag MUST not be used on nodes implementing ConfigurationCC V3 or less!`, - ZWaveErrorCodes - .ConfigurationCC_NoResetToDefaultOnLegacyDevices, - ); - } this.resetToDefault = !!options.resetToDefault; if (!options.resetToDefault) { // TODO: Default to the stored value size @@ -1883,7 +1889,7 @@ export class ConfigurationCCSet extends ConfigurationCC { public valueFormat: ConfigValueFormat | undefined; public value: ConfigValue | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const valueSize = this.resetToDefault ? 1 : this.valueSize!; const payloadLength = 2 + valueSize; this.payload = Buffer.alloc(payloadLength, 0); @@ -1923,10 +1929,10 @@ export class ConfigurationCCSet extends ConfigurationCC { ); } } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "parameter #": this.parameter, "reset to default": this.resetToDefault, @@ -1944,7 +1950,7 @@ export class ConfigurationCCSet extends ConfigurationCC { message.value = configValueToString(this.value); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1978,12 +1984,11 @@ function getResponseForBulkSet(cc: ConfigurationCCBulkSet) { @useSupervision() export class ConfigurationCCBulkSet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCBulkSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2043,7 +2048,7 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { return this._handshake; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const valueSize = this._resetToDefault ? 1 : this.valueSize; const payloadLength = 4 + valueSize * this.parameters.length; this.payload = Buffer.alloc(payloadLength, 0); @@ -2087,10 +2092,10 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { } } } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { handshake: this.handshake, "reset to default": this.resetToDefault, @@ -2109,7 +2114,7 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2118,10 +2123,9 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { @CCCommand(ConfigurationCommand.BulkReport) export class ConfigurationCCBulkReport extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); // Ensure we received enough bytes for the preamble validatePayload(this.payload.length >= 5); @@ -2149,15 +2153,15 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Store every received parameter // eslint-disable-next-line prefer-const for (let [parameter, value] of this._values.entries()) { // Check if the initial assumption of SignedInteger holds true const oldParamInformation = this.getParamInformation( - applHost, + ctx, parameter, ); if ( @@ -2175,7 +2179,7 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { } this.setValue( - applHost, + ctx, ConfigurationCCValues.paramInformation(parameter), value, ); @@ -2218,7 +2222,7 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { return this._values; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "handshake response": this._isHandshakeResponse, "default values": this._defaultValues, @@ -2234,7 +2238,7 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2249,12 +2253,11 @@ export interface ConfigurationCCBulkGetOptions extends CCCommandOptions { @expectedCCResponse(ConfigurationCCBulkReport) export class ConfigurationCCBulkGet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCBulkGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2277,16 +2280,16 @@ export class ConfigurationCCBulkGet extends ConfigurationCC { return this._parameters; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(3); this.payload.writeUInt16BE(this.parameters[0], 0); this.payload[2] = this.parameters.length; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { parameters: this.parameters.join(", ") }, }; } @@ -2302,12 +2305,11 @@ export interface ConfigurationCCNameReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.NameReport) export class ConfigurationCCNameReport extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCNameReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Parameter and # of reports must be present @@ -2330,19 +2332,19 @@ export class ConfigurationCCNameReport extends ConfigurationCC { public name: string; public readonly reportsToFollow: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Bitfield parameters that are not documented in a config file // are split into multiple partial parameters. We need to set the name for // all of them. const partialParams = this.getPartialParamInfos( - applHost, + ctx, this.parameter, ); if (partialParams.length === 0) { - this.extendParamInformation(applHost, this.parameter, undefined, { + this.extendParamInformation(ctx, this.parameter, undefined, { label: this.name, }); } else { @@ -2357,7 +2359,7 @@ export class ConfigurationCCNameReport extends ConfigurationCC { if (bitNumber != undefined) { label += ` (bit ${bitNumber})`; } - this.extendParamInformation(applHost, paramNumber, bitMask, { + this.extendParamInformation(ctx, paramNumber, bitMask, { label, }); } @@ -2366,14 +2368,14 @@ export class ConfigurationCCNameReport extends ConfigurationCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const nameBuffer = Buffer.from(this.name, "utf8"); this.payload = Buffer.allocUnsafe(3 + nameBuffer.length); this.payload.writeUInt16BE(this.parameter, 0); this.payload[2] = this.reportsToFollow; nameBuffer.copy(this.payload, 3); - return super.serialize(); + return super.serialize(ctx); } public getPartialCCSessionId(): Record | undefined { @@ -2386,8 +2388,8 @@ export class ConfigurationCCNameReport extends ConfigurationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: ConfigurationCCNameReport[], + _ctx: CCParsingContext, ): void { // Concat the name this.name = [...partials, this] @@ -2395,9 +2397,9 @@ export class ConfigurationCCNameReport extends ConfigurationCC { .reduce((prev, cur) => prev + cur, ""); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter, name: this.name, @@ -2411,10 +2413,9 @@ export class ConfigurationCCNameReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCNameReport) export class ConfigurationCCNameGet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.parameter = this.payload.readUInt16BE(0); @@ -2425,15 +2426,15 @@ export class ConfigurationCCNameGet extends ConfigurationCC { public parameter: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); this.payload.writeUInt16BE(this.parameter, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter }, }; } @@ -2449,12 +2450,11 @@ export interface ConfigurationCCInfoReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.InfoReport) export class ConfigurationCCInfoReport extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCInfoReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Parameter and # of reports must be present @@ -2477,15 +2477,15 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { public info: string; public readonly reportsToFollow: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Bitfield parameters that are not documented in a config file // are split into multiple partial parameters. We need to set the description for // all of them. However, these can get very long, so we put the reported // description on the first partial param, and refer to it from the others const partialParams = this.getPartialParamInfos( - applHost, + ctx, this.parameter, ).sort( (a, b) => @@ -2494,7 +2494,7 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { ); if (partialParams.length === 0) { - this.extendParamInformation(applHost, this.parameter, undefined, { + this.extendParamInformation(ctx, this.parameter, undefined, { description: this.info, }); } else { @@ -2509,7 +2509,7 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { ? `Refer to ${firstParamLabel}` : this.info; - this.extendParamInformation(applHost, paramNumber, bitMask, { + this.extendParamInformation(ctx, paramNumber, bitMask, { description, }); @@ -2517,7 +2517,7 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { // following partial params if (firstParamLabel == undefined) { firstParamLabel = - this.getParamInformation(applHost, paramNumber, bitMask) + this.getParamInformation(ctx, paramNumber, bitMask) .label ?? `parameter ${paramNumber} - ${bitMask}`; } } @@ -2526,14 +2526,14 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const infoBuffer = Buffer.from(this.info, "utf8"); this.payload = Buffer.allocUnsafe(3 + infoBuffer.length); this.payload.writeUInt16BE(this.parameter, 0); this.payload[2] = this.reportsToFollow; infoBuffer.copy(this.payload, 3); - return super.serialize(); + return super.serialize(ctx); } public getPartialCCSessionId(): Record | undefined { @@ -2546,8 +2546,8 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: ConfigurationCCInfoReport[], + _ctx: CCParsingContext, ): void { // Concat the info this.info = [...partials, this] @@ -2555,9 +2555,9 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { .reduce((prev, cur) => prev + cur, ""); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter, info: this.info, @@ -2571,10 +2571,9 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCInfoReport) export class ConfigurationCCInfoGet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.parameter = this.payload.readUInt16BE(0); @@ -2585,15 +2584,15 @@ export class ConfigurationCCInfoGet extends ConfigurationCC { public parameter: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); this.payload.writeUInt16BE(this.parameter, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter }, }; } @@ -2619,12 +2618,11 @@ export interface ConfigurationCCPropertiesReportOptions @CCCommand(ConfigurationCommand.PropertiesReport) export class ConfigurationCCPropertiesReport extends ConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ConfigurationCCPropertiesReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -2664,18 +2662,13 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { this.valueFormat, ); } - if (this.version < 4) { - // Read the last 2 bytes to work around nodes not omitting min/max value when their size is 0 - this.nextParameter = this.payload.readUInt16BE( - this.payload.length - 2, - ); - } else { - this.nextParameter = this.payload.readUInt16BE( - nextParameterOffset, - ); - // Ensure the payload contains a byte for the 2nd option flags - validatePayload(this.payload.length >= nextParameterOffset + 3); + this.nextParameter = this.payload.readUInt16BE( + nextParameterOffset, + ); + + if (this.payload.length >= nextParameterOffset + 3) { + // V4 adds an options byte after the next parameter and two bits in byte 2 const options1 = this.payload[2]; const options2 = this.payload[3 + 3 * this.valueSize + 2]; this.altersCapabilities = !!(options1 & 0b1000_0000); @@ -2716,8 +2709,8 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // If we actually received parameter info, store it if (this.valueSize > 0) { @@ -2736,7 +2729,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { if (this.valueFormat !== ConfigValueFormat.BitField) { // Do not override param information from a config file - if (!this.paramExistsInConfigFile(applHost, this.parameter)) { + if (!this.paramExistsInConfigFile(ctx, this.parameter)) { const paramInfo = stripUndefined( { ...baseInfo, @@ -2747,7 +2740,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { ); this.extendParamInformation( - applHost, + ctx, this.parameter, undefined, paramInfo, @@ -2763,7 +2756,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { !!(mask & bits) // Do not override param information from a config file && !this.paramExistsInConfigFile( - applHost, + ctx, this.parameter, mask, ) @@ -2778,7 +2771,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { ); this.extendParamInformation( - applHost, + ctx, this.parameter, mask, paramInfo, @@ -2807,7 +2800,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { public isAdvanced: MaybeNotKnown; public noBulkSupport: MaybeNotKnown; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe( 3 // preamble + 3 * this.valueSize // min, max, default value @@ -2855,10 +2848,10 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { | (this.noBulkSupport ? 0b10 : 0); this.payload[offset] = options2; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "parameter #": this.parameter, "next param #": this.nextParameter, @@ -2890,7 +2883,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { message["bulk support"] = !this.noBulkSupport; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2900,10 +2893,9 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCPropertiesReport) export class ConfigurationCCPropertiesGet extends ConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.parameter = this.payload.readUInt16BE(0); @@ -2914,15 +2906,15 @@ export class ConfigurationCCPropertiesGet extends ConfigurationCC { public parameter: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); this.payload.writeUInt16BE(this.parameter, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "parameter #": this.parameter }, }; } diff --git a/packages/cc/src/cc/DeviceResetLocallyCC.ts b/packages/cc/src/cc/DeviceResetLocallyCC.ts index 8a028fa9e3c7..105ac53672e5 100644 --- a/packages/cc/src/cc/DeviceResetLocallyCC.ts +++ b/packages/cc/src/cc/DeviceResetLocallyCC.ts @@ -4,7 +4,6 @@ import { TransmitOptions, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost } from "@zwave-js/host/safe"; import { CCAPI } from "../lib/API"; import { CommandClass, @@ -39,7 +38,7 @@ export class DeviceResetLocallyCCAPI extends CCAPI { DeviceResetLocallyCommand.Notification, ); - const cc = new DeviceResetLocallyCCNotification(this.applHost, { + const cc = new DeviceResetLocallyCCNotification({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); @@ -47,7 +46,7 @@ export class DeviceResetLocallyCCAPI extends CCAPI { try { // This command is sent immediately before a hard reset of the controller. // If we don't wait for a callback (ack), the controller locks up when hard-resetting. - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Do not fall back to explorer frames transmitOptions: TransmitOptions.ACK @@ -73,8 +72,8 @@ export class DeviceResetLocallyCC extends CommandClass { @CCCommand(DeviceResetLocallyCommand.Notification) export class DeviceResetLocallyCCNotification extends DeviceResetLocallyCC { - public constructor(host: ZWaveHost, options: CommandClassOptions) { - super(host, options); + public constructor(options: CommandClassOptions) { + super(options); if (gotDeserializationOptions(options)) { // We need to make sure this doesn't get parsed accidentally, e.g. because of a bit flip diff --git a/packages/cc/src/cc/DoorLockCC.ts b/packages/cc/src/cc/DoorLockCC.ts index 58e393219bd2..50f11ac03ad8 100644 --- a/packages/cc/src/cc/DoorLockCC.ts +++ b/packages/cc/src/cc/DoorLockCC.ts @@ -1,7 +1,7 @@ import { CommandClasses, Duration, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -15,11 +15,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { isArray } from "alcalzone-shared/typeguards"; @@ -37,6 +33,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -253,10 +252,10 @@ export const DoorLockCCValues = Object.freeze({ }); function shouldAutoCreateLatchStatusValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.latchSupported.endpoint(endpoint.index), @@ -264,10 +263,10 @@ function shouldAutoCreateLatchStatusValue( } function shouldAutoCreateBoltStatusValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.boltSupported.endpoint(endpoint.index), @@ -275,10 +274,10 @@ function shouldAutoCreateBoltStatusValue( } function shouldAutoCreateDoorStatusValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.doorSupported.endpoint(endpoint.index), @@ -286,10 +285,10 @@ function shouldAutoCreateDoorStatusValue( } function shouldAutoCreateTwistAssistConfigValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.twistAssistSupported.endpoint(endpoint.index), @@ -297,10 +296,10 @@ function shouldAutoCreateTwistAssistConfigValue( } function shouldAutoCreateBlockToBlockConfigValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.blockToBlockSupported.endpoint(endpoint.index), @@ -308,10 +307,10 @@ function shouldAutoCreateBlockToBlockConfigValue( } function shouldAutoCreateAutoRelockConfigValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.autoRelockSupported.endpoint(endpoint.index), @@ -319,10 +318,10 @@ function shouldAutoCreateAutoRelockConfigValue( } function shouldAutoCreateHoldAndReleaseConfigValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; return !!valueDB.getValue( DoorLockCCValues.holdAndReleaseSupported.endpoint(endpoint.index), @@ -463,11 +462,11 @@ export class DoorLockCCAPI extends PhysicalCCAPI { DoorLockCommand.CapabilitiesGet, ); - const cc = new DoorLockCCCapabilitiesGet(this.applHost, { + const cc = new DoorLockCCCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCCapabilitiesReport >( cc, @@ -497,11 +496,11 @@ export class DoorLockCCAPI extends PhysicalCCAPI { DoorLockCommand.OperationGet, ); - const cc = new DoorLockCCOperationGet(this.applHost, { + const cc = new DoorLockCCOperationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCOperationReport >( cc, @@ -531,12 +530,12 @@ export class DoorLockCCAPI extends PhysicalCCAPI { DoorLockCommand.OperationSet, ); - const cc = new DoorLockCCOperationSet(this.applHost, { + const cc = new DoorLockCCOperationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, mode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -548,12 +547,12 @@ export class DoorLockCCAPI extends PhysicalCCAPI { DoorLockCommand.ConfigurationSet, ); - const cc = new DoorLockCCConfigurationSet(this.applHost, { + const cc = new DoorLockCCConfigurationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...configuration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -563,11 +562,11 @@ export class DoorLockCCAPI extends PhysicalCCAPI { DoorLockCommand.ConfigurationGet, ); - const cc = new DoorLockCCConfigurationGet(this.applHost, { + const cc = new DoorLockCCConfigurationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCConfigurationReport >( cc, @@ -594,18 +593,20 @@ export class DoorLockCCAPI extends PhysicalCCAPI { export class DoorLockCC extends CommandClass { declare ccCommand: DoorLockCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Door Lock"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -620,8 +621,8 @@ export class DoorLockCC extends CommandClass { let boltSupported = true; let latchSupported = true; - if (this.version >= 4) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 4) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting lock capabilities...", direction: "outbound", @@ -653,7 +654,7 @@ supports auto-relock: ${resp.autoRelockSupported} supports hold-and-release: ${resp.holdAndReleaseSupported} supports twist assist: ${resp.twistAssistSupported} supports block to block: ${resp.blockToBlockSupported}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -665,7 +666,7 @@ supports block to block: ${resp.blockToBlockSupported}`; // Update metadata of settable states const targetModeValue = DoorLockCCValues.targetMode; - this.setMetadata(applHost, targetModeValue, { + this.setMetadata(ctx, targetModeValue, { ...targetModeValue.meta, states: enumValuesToMetadataStates( DoorLockMode, @@ -674,7 +675,7 @@ supports block to block: ${resp.blockToBlockSupported}`; }); const operationTypeValue = DoorLockCCValues.operationType; - this.setMetadata(applHost, operationTypeValue, { + this.setMetadata(ctx, operationTypeValue, { ...operationTypeValue.meta, states: enumValuesToMetadataStates( DoorLockOperationType, @@ -689,48 +690,50 @@ supports block to block: ${resp.blockToBlockSupported}`; if (!hadCriticalTimeout) { // Save support information for the status values const doorStatusValue = DoorLockCCValues.doorStatus; - if (doorSupported) this.setMetadata(applHost, doorStatusValue); + if (doorSupported) this.setMetadata(ctx, doorStatusValue); this.setValue( - applHost, + ctx, DoorLockCCValues.doorSupported, doorSupported, ); const latchStatusValue = DoorLockCCValues.latchStatus; - if (latchSupported) this.setMetadata(applHost, latchStatusValue); + if (latchSupported) this.setMetadata(ctx, latchStatusValue); this.setValue( - applHost, + ctx, DoorLockCCValues.latchSupported, latchSupported, ); const boltStatusValue = DoorLockCCValues.boltStatus; - if (boltSupported) this.setMetadata(applHost, boltStatusValue); + if (boltSupported) this.setMetadata(ctx, boltStatusValue); this.setValue( - applHost, + ctx, DoorLockCCValues.boltSupported, boltSupported, ); } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - if (!hadCriticalTimeout) this.setInterviewComplete(applHost, true); + if (!hadCriticalTimeout) this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Door Lock"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting lock configuration...", direction: "outbound", @@ -760,7 +763,7 @@ inside handles can open door: ${ .map(String) .join(", ") }`; - if (this.version >= 4) { + if (api.version >= 4) { logMessage += ` auto-relock time ${config.autoRelockTime ?? "-"} seconds hold-and-release time ${config.holdAndReleaseTime ?? "-"} seconds @@ -768,14 +771,14 @@ twist assist ${!!config.twistAssist} block to block ${!!config.blockToBlock}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting current lock status...", direction: "outbound", @@ -805,7 +808,7 @@ bolt status: ${status.boltStatus}`; logMessage += ` latch status: ${status.latchStatus}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -823,12 +826,11 @@ export interface DoorLockCCOperationSetOptions extends CCCommandOptions { @useSupervision() export class DoorLockCCOperationSet extends DoorLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | DoorLockCCOperationSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -848,14 +850,14 @@ export class DoorLockCCOperationSet extends DoorLockCC { public mode: DoorLockMode; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.mode]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "target mode": getEnumMemberName(DoorLockMode, this.mode), }, @@ -866,10 +868,9 @@ export class DoorLockCCOperationSet extends DoorLockCC { @CCCommand(DoorLockCommand.OperationReport) export class DoorLockCCOperationReport extends DoorLockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 5); this.currentMode = this.payload[0]; @@ -897,45 +898,45 @@ export class DoorLockCCOperationReport extends DoorLockCC { this.lockTimeout = lockTimeoutSeconds + lockTimeoutMinutes * 60; } - if (this.version >= 3 && this.payload.length >= 7) { + if (this.payload.length >= 7) { this.targetMode = this.payload[5]; this.duration = Duration.parseReport(this.payload[6]); } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Only store the door/bolt/latch status if the lock supports it const supportsDoorStatus = !!this.getValue( - applHost, + ctx, DoorLockCCValues.doorSupported, ); if (supportsDoorStatus) { this.setValue( - applHost, + ctx, DoorLockCCValues.doorStatus, this.doorStatus, ); } const supportsBoltStatus = !!this.getValue( - applHost, + ctx, DoorLockCCValues.boltSupported, ); if (supportsBoltStatus) { this.setValue( - applHost, + ctx, DoorLockCCValues.boltStatus, this.boltStatus, ); } const supportsLatchStatus = !!this.getValue( - applHost, + ctx, DoorLockCCValues.latchSupported, ); if (supportsLatchStatus) { this.setValue( - applHost, + ctx, DoorLockCCValues.latchStatus, this.latchStatus, ); @@ -966,7 +967,7 @@ export class DoorLockCCOperationReport extends DoorLockCC { @ccValue(DoorLockCCValues.lockTimeout) public readonly lockTimeout?: number; // in seconds - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "current mode": getEnumMemberName(DoorLockMode, this.currentMode), "active outside handles": this.outsideHandlesCanOpenDoor.join(", "), @@ -996,7 +997,7 @@ export class DoorLockCCOperationReport extends DoorLockCC { message["lock timeout"] = `${this.lockTimeout} seconds`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1009,10 +1010,9 @@ export class DoorLockCCOperationGet extends DoorLockCC {} @CCCommand(DoorLockCommand.ConfigurationReport) export class DoorLockCCConfigurationReport extends DoorLockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); this.operationType = this.payload[0]; @@ -1036,7 +1036,7 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { + lockTimeoutMinutes * 60; } } - if (this.version >= 4 && this.payload.length >= 5) { + if (this.payload.length >= 5) { this.autoRelockTime = this.payload.readUInt16BE(4); this.holdAndReleaseTime = this.payload.readUInt16BE(6); @@ -1065,50 +1065,50 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { public readonly twistAssist?: boolean; public readonly blockToBlock?: boolean; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Only store the autoRelockTime etc. params if the lock supports it const supportsAutoRelock = !!this.getValue( - applHost, + ctx, DoorLockCCValues.autoRelockSupported, ); if (supportsAutoRelock) { this.setValue( - applHost, + ctx, DoorLockCCValues.autoRelockTime, this.autoRelockTime, ); } const supportsHoldAndRelease = !!this.getValue( - applHost, + ctx, DoorLockCCValues.holdAndReleaseSupported, ); if (supportsHoldAndRelease) { this.setValue( - applHost, + ctx, DoorLockCCValues.holdAndReleaseTime, this.holdAndReleaseTime, ); } const supportsTwistAssist = !!this.getValue( - applHost, + ctx, DoorLockCCValues.twistAssistSupported, ); if (supportsTwistAssist) { this.setValue( - applHost, + ctx, DoorLockCCValues.twistAssist, this.twistAssist, ); } const supportsBlockToBlock = !!this.getValue( - applHost, + ctx, DoorLockCCValues.blockToBlockSupported, ); if (supportsBlockToBlock) { this.setValue( - applHost, + ctx, DoorLockCCValues.blockToBlock, this.blockToBlock, ); @@ -1117,7 +1117,7 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { return true; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "operation type": getEnumMemberName( DoorLockOperationType, @@ -1148,7 +1148,7 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { message["block-to-block enabled"] = this.blockToBlock; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1184,12 +1184,11 @@ export type DoorLockCCConfigurationSetOptions = @useSupervision() export class DoorLockCCConfigurationSet extends DoorLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & DoorLockCCConfigurationSetOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1219,7 +1218,7 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { public twistAssist?: boolean; public blockToBlock?: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const insideHandles = isArray( this.insideHandlesCanOpenDoorConfiguration, ) @@ -1269,10 +1268,10 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { 6, ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const insideHandles = isArray( this.insideHandlesCanOpenDoorConfiguration, ) @@ -1311,7 +1310,7 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { message["enable block-to-block"] = this.blockToBlock; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1320,10 +1319,9 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { @CCCommand(DoorLockCommand.CapabilitiesReport) export class DoorLockCCCapabilitiesReport extends DoorLockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); // parse variable length operation type bit mask validatePayload(this.payload.length >= 1); @@ -1396,9 +1394,9 @@ export class DoorLockCCCapabilitiesReport extends DoorLockCC { @ccValue(DoorLockCCValues.blockToBlockSupported) public readonly blockToBlockSupported: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { door: this.doorSupported, bolt: this.boltSupported, diff --git a/packages/cc/src/cc/DoorLockLoggingCC.ts b/packages/cc/src/cc/DoorLockLoggingCC.ts index a88bd9585c9b..cbd016bef743 100644 --- a/packages/cc/src/cc/DoorLockLoggingCC.ts +++ b/packages/cc/src/cc/DoorLockLoggingCC.ts @@ -8,11 +8,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { isPrintableASCII, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -20,6 +16,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -126,11 +124,11 @@ export class DoorLockLoggingCCAPI extends PhysicalCCAPI { DoorLockLoggingCommand.RecordsSupportedGet, ); - const cc = new DoorLockLoggingCCRecordsSupportedGet(this.applHost, { + const cc = new DoorLockLoggingCCRecordsSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockLoggingCCRecordsSupportedReport >( cc, @@ -149,12 +147,12 @@ export class DoorLockLoggingCCAPI extends PhysicalCCAPI { DoorLockLoggingCommand.RecordGet, ); - const cc = new DoorLockLoggingCCRecordGet(this.applHost, { + const cc = new DoorLockLoggingCCRecordGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, recordNumber, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockLoggingCCRecordReport >( cc, @@ -170,33 +168,37 @@ export class DoorLockLoggingCCAPI extends PhysicalCCAPI { export class DoorLockLoggingCC extends CommandClass { declare ccCommand: DoorLockLoggingCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Door Lock Logging"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported number of records...", direction: "outbound", @@ -204,7 +206,7 @@ export class DoorLockLoggingCC extends CommandClass { const recordsCount = await api.getRecordsCount(); if (!recordsCount) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Door Lock Logging records count query timed out, skipping interview...", @@ -216,7 +218,7 @@ export class DoorLockLoggingCC extends CommandClass { const recordsCountLogMessage = `supports ${recordsCount} record${ recordsCount === 1 ? "" : "s" }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: recordsCountLogMessage, direction: "inbound", @@ -227,10 +229,9 @@ export class DoorLockLoggingCC extends CommandClass { @CCCommand(DoorLockLoggingCommand.RecordsSupportedReport) export class DoorLockLoggingCCRecordsSupportedReport extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.recordsCount = this.payload[0]; @@ -239,9 +240,9 @@ export class DoorLockLoggingCCRecordsSupportedReport extends DoorLockLoggingCC { @ccValue(DoorLockLoggingCCValues.recordsCount) public readonly recordsCount: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported no. of records": this.recordsCount, }, @@ -263,10 +264,9 @@ export class DoorLockLoggingCCRecordsSupportedGet extends DoorLockLoggingCC {} @CCCommand(DoorLockLoggingCommand.RecordReport) export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 11); this.recordNumber = this.payload[0]; @@ -315,7 +315,7 @@ export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { public readonly recordNumber: number; public readonly record?: DoorLockLoggingRecord; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (!this.record) { @@ -338,7 +338,7 @@ export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -366,12 +366,11 @@ function testResponseForDoorLockLoggingRecordGet( ) export class DoorLockLoggingCCRecordGet extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | DoorLockLoggingCCRecordGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -384,14 +383,14 @@ export class DoorLockLoggingCCRecordGet extends DoorLockLoggingCC { public recordNumber: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.recordNumber]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "record number": this.recordNumber }, }; } diff --git a/packages/cc/src/cc/EnergyProductionCC.ts b/packages/cc/src/cc/EnergyProductionCC.ts index 2871a514a9e2..6754b286b5be 100644 --- a/packages/cc/src/cc/EnergyProductionCC.ts +++ b/packages/cc/src/cc/EnergyProductionCC.ts @@ -8,11 +8,7 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host"; import { getEnumMemberName, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -25,6 +21,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -109,12 +108,12 @@ export class EnergyProductionCCAPI extends CCAPI { EnergyProductionCommand.Get, ); - const cc = new EnergyProductionCCGet(this.applHost, { + const cc = new EnergyProductionCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EnergyProductionCCReport >( cc, @@ -132,28 +131,32 @@ export class EnergyProductionCCAPI extends CCAPI { export class EnergyProductionCC extends CommandClass { declare ccCommand: EnergyProductionCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Query current values - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Energy Production"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, @@ -167,7 +170,7 @@ export class EnergyProductionCC extends CommandClass { EnergyProductionParameter["Total Time"], ] as const ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying energy production (${ getEnumMemberName( @@ -193,12 +196,11 @@ export interface EnergyProductionCCReportOptions extends CCCommandOptions { @CCCommand(EnergyProductionCommand.Report) export class EnergyProductionCCReport extends EnergyProductionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | EnergyProductionCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.parameter = this.payload[0]; @@ -218,11 +220,11 @@ export class EnergyProductionCCReport extends EnergyProductionCC { public readonly scale: EnergyProductionScale; public readonly value: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const valueValue = EnergyProductionCCValues.value(this.parameter); - this.setMetadata(applHost, valueValue, { + this.setMetadata(ctx, valueValue, { ...valueValue.meta, unit: this.scale.unit, ccSpecific: { @@ -230,22 +232,22 @@ export class EnergyProductionCCReport extends EnergyProductionCC { scale: this.scale.key, }, }); - this.setValue(applHost, valueValue, this.value); + this.setValue(ctx, valueValue, this.value); return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.parameter]), encodeFloatWithScale(this.value, this.scale.key), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { [ getEnumMemberName( @@ -277,12 +279,11 @@ function testResponseForEnergyProductionGet( ) export class EnergyProductionCCGet extends EnergyProductionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | EnergyProductionCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.parameter = this.payload[0]; @@ -293,14 +294,14 @@ export class EnergyProductionCCGet extends EnergyProductionCC { public parameter: EnergyProductionParameter; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.parameter]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { parameter: getEnumMemberName( EnergyProductionParameter, diff --git a/packages/cc/src/cc/EntryControlCC.ts b/packages/cc/src/cc/EntryControlCC.ts index b47a03500c76..e48b916c9aae 100644 --- a/packages/cc/src/cc/EntryControlCC.ts +++ b/packages/cc/src/cc/EntryControlCC.ts @@ -13,11 +13,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { buffer2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -33,6 +29,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -113,11 +112,11 @@ export class EntryControlCCAPI extends CCAPI { EntryControlCommand.KeySupportedGet, ); - const cc = new EntryControlCCKeySupportedGet(this.applHost, { + const cc = new EntryControlCCKeySupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCKeySupportedReport >( cc, @@ -133,11 +132,11 @@ export class EntryControlCCAPI extends CCAPI { EntryControlCommand.EventSupportedGet, ); - const cc = new EntryControlCCEventSupportedGet(this.applHost, { + const cc = new EntryControlCCEventSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCEventSupportedReport >( cc, @@ -162,11 +161,11 @@ export class EntryControlCCAPI extends CCAPI { EntryControlCommand.ConfigurationGet, ); - const cc = new EntryControlCCConfigurationGet(this.applHost, { + const cc = new EntryControlCCConfigurationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCConfigurationReport >( cc, @@ -187,13 +186,13 @@ export class EntryControlCCAPI extends CCAPI { EntryControlCommand.ConfigurationGet, ); - const cc = new EntryControlCCConfigurationSet(this.applHost, { + const cc = new EntryControlCCConfigurationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, keyCacheSize, keyCacheTimeout, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -269,18 +268,20 @@ export class EntryControlCC extends CommandClass { ]; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Entry Control"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -290,13 +291,13 @@ export class EntryControlCC extends CommandClass { // we must associate ourselves with that channel try { await ccUtils.assignLifelineIssueingCommand( - applHost, + ctx, endpoint, this.ccId, EntryControlCommand.Notification, ); } catch { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Configuring associations to receive ${ getCCName( @@ -307,7 +308,7 @@ export class EntryControlCC extends CommandClass { }); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting entry control supported keys...", direction: "outbound", @@ -315,7 +316,7 @@ export class EntryControlCC extends CommandClass { const supportedKeys = await api.getSupportedKeys(); if (supportedKeys) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received entry control supported keys: ${supportedKeys.toString()}`, @@ -323,7 +324,7 @@ export class EntryControlCC extends CommandClass { }); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting entry control supported events...", direction: "outbound", @@ -331,7 +332,7 @@ export class EntryControlCC extends CommandClass { const eventCapabilities = await api.getEventCapabilities(); if (eventCapabilities) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received entry control supported keys: data types: ${ @@ -352,24 +353,26 @@ max key cache timeout: ${eventCapabilities.maxKeyCacheTimeout} seconds`, }); } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Entry Control"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting entry control configuration...", direction: "outbound", @@ -377,7 +380,7 @@ max key cache timeout: ${eventCapabilities.maxKeyCacheTimeout} seconds`, const conf = await api.getConfiguration(); if (conf) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received entry control configuration: key cache size: ${conf.keyCacheSize} @@ -391,10 +394,9 @@ key cache timeout: ${conf.keyCacheTimeout} seconds`, @CCCommand(EntryControlCommand.Notification) export class EntryControlCCNotification extends EntryControlCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); this.sequenceNumber = this.payload[0]; @@ -410,7 +412,7 @@ export class EntryControlCCNotification extends EntryControlCC { // But as always - manufacturers don't care and send ASCII data with 0 bytes... // We also need to disable the strict validation for some devices to make them work - const noStrictValidation = !!this.host.getDeviceConfig?.( + const noStrictValidation = !!options.context.getDeviceConfig?.( this.nodeId as number, )?.compat?.disableStrictEntryControlDataValidation; @@ -463,7 +465,7 @@ export class EntryControlCCNotification extends EntryControlCC { public readonly eventType: EntryControlEventTypes; public readonly eventData?: Buffer | string; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "sequence number": this.sequenceNumber, "data type": this.dataType, @@ -484,7 +486,7 @@ export class EntryControlCCNotification extends EntryControlCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -493,10 +495,9 @@ export class EntryControlCCNotification extends EntryControlCC { @CCCommand(EntryControlCommand.KeySupportedReport) export class EntryControlCCKeySupportedReport extends EntryControlCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); const length = this.payload[0]; @@ -510,9 +511,9 @@ export class EntryControlCCKeySupportedReport extends EntryControlCC { @ccValue(EntryControlCCValues.supportedKeys) public readonly supportedKeys: readonly number[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported keys": this.supportedKeys.toString() }, }; } @@ -525,10 +526,9 @@ export class EntryControlCCKeySupportedGet extends EntryControlCC {} @CCCommand(EntryControlCommand.EventSupportedReport) export class EntryControlCCEventSupportedReport extends EntryControlCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); const dataTypeLength = this.payload[0] & 0b11; @@ -566,19 +566,19 @@ export class EntryControlCCEventSupportedReport extends EntryControlCC { this.maxKeyCacheTimeout = this.payload[offset + 3]; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Store min/max cache size and timeout as metadata const keyCacheSizeValue = EntryControlCCValues.keyCacheSize; - this.setMetadata(applHost, keyCacheSizeValue, { + this.setMetadata(ctx, keyCacheSizeValue, { ...keyCacheSizeValue.meta, min: this.minKeyCacheSize, max: this.maxKeyCacheSize, }); const keyCacheTimeoutValue = EntryControlCCValues.keyCacheTimeout; - this.setMetadata(applHost, keyCacheTimeoutValue, { + this.setMetadata(ctx, keyCacheTimeoutValue, { ...keyCacheTimeoutValue.meta, min: this.minKeyCacheTimeout, max: this.maxKeyCacheTimeout, @@ -598,9 +598,9 @@ export class EntryControlCCEventSupportedReport extends EntryControlCC { public readonly minKeyCacheTimeout: number; public readonly maxKeyCacheTimeout: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported data types": this.supportedDataTypes .map((dt) => EntryControlDataTypes[dt]) @@ -624,10 +624,9 @@ export class EntryControlCCEventSupportedGet extends EntryControlCC {} @CCCommand(EntryControlCommand.ConfigurationReport) export class EntryControlCCConfigurationReport extends EntryControlCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); @@ -642,9 +641,9 @@ export class EntryControlCCConfigurationReport extends EntryControlCC { @ccValue(EntryControlCCValues.keyCacheTimeout) public readonly keyCacheTimeout: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "key cache size": this.keyCacheSize, "key cache timeout": this.keyCacheTimeout, @@ -669,12 +668,11 @@ export interface EntryControlCCConfigurationSetOptions @useSupervision() export class EntryControlCCConfigurationSet extends EntryControlCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | EntryControlCCConfigurationSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -690,14 +688,14 @@ export class EntryControlCCConfigurationSet extends EntryControlCC { public readonly keyCacheSize: number; public readonly keyCacheTimeout: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.keyCacheSize, this.keyCacheTimeout]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "key cache size": this.keyCacheSize, "key cache timeout": this.keyCacheTimeout, diff --git a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts index c9725d40feaf..0d1ad2a74cec 100644 --- a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts +++ b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts @@ -9,11 +9,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { type AllOrNone, getEnumMemberName, @@ -26,6 +22,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -41,7 +39,6 @@ import { V } from "../lib/Values"; import { FirmwareDownloadStatus, FirmwareUpdateActivationStatus, - type FirmwareUpdateInitResult, type FirmwareUpdateMetaData, FirmwareUpdateMetaDataCommand, FirmwareUpdateRequestStatus, @@ -113,11 +110,11 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { FirmwareUpdateMetaDataCommand.MetaDataGet, ); - const cc = new FirmwareUpdateMetaDataCCMetaDataGet(this.applHost, { + const cc = new FirmwareUpdateMetaDataCCMetaDataGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FirmwareUpdateMetaDataCCMetaDataReport >( cc, @@ -149,55 +146,38 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { FirmwareUpdateMetaDataCommand.Report, ); - const cc = new FirmwareUpdateMetaDataCCMetaDataReport(this.applHost, { + const cc = new FirmwareUpdateMetaDataCCMetaDataReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** * Requests the device to start the firmware update process. - * WARNING: This method may wait up to 60 seconds for a reply. + * This does not wait for the reply - that is up to the caller of this method. */ @validateArgs() public async requestUpdate( options: FirmwareUpdateMetaDataCCRequestGetOptions, - ): Promise { + ): Promise { this.assertSupportsCommand( FirmwareUpdateMetaDataCommand, FirmwareUpdateMetaDataCommand.RequestGet, ); - const cc = new FirmwareUpdateMetaDataCCRequestGet(this.applHost, { + const cc = new FirmwareUpdateMetaDataCCRequestGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - // Since the response may take longer than with other commands, - // we do not use the built-in waiting functionality, which would block - // all other communication. - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Do not wait for Nonce Reports s2VerifyDelivery: false, }); - const result = await this.applHost - .waitForCommand< - FirmwareUpdateMetaDataCCRequestReport - >( - (cc) => - cc instanceof FirmwareUpdateMetaDataCCRequestReport - && cc.nodeId === this.endpoint.nodeId, - 60000, - ); - return pick(result, [ - "status", - "resume", - "nonSecureTransfer", - ]); } /** @@ -214,14 +194,14 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { FirmwareUpdateMetaDataCommand.Report, ); - const cc = new FirmwareUpdateMetaDataCCReport(this.applHost, { + const cc = new FirmwareUpdateMetaDataCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, reportNumber: fragmentNumber, isLast: isLastFragment, firmwareData: data, }); - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Do not wait for Nonce Reports s2VerifyDelivery: false, @@ -238,12 +218,12 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI { FirmwareUpdateMetaDataCommand.ActivationSet, ); - const cc = new FirmwareUpdateMetaDataCCActivationSet(this.applHost, { + const cc = new FirmwareUpdateMetaDataCCActivationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FirmwareUpdateMetaDataCCActivationReport >( cc, @@ -263,24 +243,26 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { return true; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Firmware Update Meta Data"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying firmware update capabilities...", direction: "outbound", @@ -305,13 +287,13 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { } else { logMessage += `\nfirmware upgradeable: false`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Firmware update capability query timed out", direction: "inbound", @@ -319,7 +301,7 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -344,7 +326,6 @@ export class FirmwareUpdateMetaDataCCMetaDataReport implements FirmwareUpdateMetaData { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ( @@ -352,7 +333,7 @@ export class FirmwareUpdateMetaDataCCMetaDataReport & CCCommandOptions ), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 6); @@ -363,7 +344,8 @@ export class FirmwareUpdateMetaDataCCMetaDataReport this.firmwareUpgradable = this.payload[6] === 0xff || this.payload[6] == undefined; - if (this.version >= 3 && this.payload.length >= 10) { + if (this.payload.length >= 10) { + // V3+ this.maxFragmentSize = this.payload.readUInt16BE(8); // Read variable length list of additional firmwares const numAdditionalFirmwares = this.payload[7]; @@ -379,24 +361,22 @@ export class FirmwareUpdateMetaDataCCMetaDataReport this.additionalFirmwareIDs = additionalFirmwareIDs; // Read hardware version (if it exists) let offset = 10 + 2 * numAdditionalFirmwares; - if (this.version >= 5 && this.payload.length >= offset + 1) { + if (this.payload.length >= offset + 1) { + // V5+ this.hardwareVersion = this.payload[offset]; offset++; - if ( - this.version >= 6 && this.payload.length >= offset + 1 - ) { + if (this.payload.length >= offset + 1) { + // V6+ const capabilities = this.payload[offset]; offset++; this.continuesToFunction = !!(capabilities & 0b1); - if (this.version >= 7) { - this.supportsActivation = !!(capabilities & 0b10); - } - if (this.version >= 8) { - this.supportsResuming = !!(capabilities & 0b1000); - this.supportsNonSecureTransfer = - !!(capabilities & 0b100); - } + // V7+ + this.supportsActivation = !!(capabilities & 0b10); + // V8+ + this.supportsResuming = !!(capabilities & 0b1000); + this.supportsNonSecureTransfer = + !!(capabilities & 0b100); } } } @@ -433,7 +413,7 @@ export class FirmwareUpdateMetaDataCCMetaDataReport @ccValue(FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer) public readonly supportsNonSecureTransfer?: MaybeNotKnown; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.alloc( 12 + 2 * this.additionalFirmwareIDs.length, ); @@ -454,10 +434,10 @@ export class FirmwareUpdateMetaDataCCMetaDataReport | (this.supportsNonSecureTransfer ? 0b100 : 0) | (this.supportsResuming ? 0b1000 : 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "manufacturer id": this.manufacturerId, "firmware id": this.firmwareId, @@ -490,7 +470,7 @@ export class FirmwareUpdateMetaDataCCMetaDataReport } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -507,10 +487,9 @@ export class FirmwareUpdateMetaDataCCRequestReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.status = this.payload[0]; if (this.payload.length >= 2) { @@ -523,7 +502,7 @@ export class FirmwareUpdateMetaDataCCRequestReport public resume?: boolean; public nonSecureTransfer?: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { status: getEnumMemberName( FirmwareUpdateRequestStatus, @@ -537,7 +516,7 @@ export class FirmwareUpdateMetaDataCCRequestReport message["non-secure transfer"] = this.nonSecureTransfer; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -570,12 +549,11 @@ export class FirmwareUpdateMetaDataCCRequestGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (FirmwareUpdateMetaDataCCRequestGetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -607,36 +585,24 @@ export class FirmwareUpdateMetaDataCCRequestGet public resume?: boolean; public nonSecureTransfer?: boolean; - public serialize(): Buffer { - const isV3 = this.version >= 3 - && this.firmwareTarget != undefined - && this.fragmentSize != undefined; - const isV4 = isV3 && this.version >= 4; - const isV5 = isV4 - && this.version >= 5 - && this.hardwareVersion != undefined; - this.payload = Buffer.allocUnsafe( - 6 + (isV3 ? 3 : 0) + (isV4 ? 1 : 0) + (isV5 ? 1 : 0), - ); + public serialize(ctx: CCEncodingContext): Buffer { + this.payload = Buffer.alloc(11, 0); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload.writeUInt16BE(this.checksum, 4); - if (isV3) { - this.payload[6] = this.firmwareTarget!; - this.payload.writeUInt16BE(this.fragmentSize!, 7); - } - if (isV4) { - this.payload[9] = (this.activation ? 0b1 : 0) - | (this.nonSecureTransfer ? 0b10 : 0) - | (this.resume ? 0b100 : 0); - } - if (isV5) { - this.payload[10] = this.hardwareVersion!; - } - return super.serialize(); + this.payload[6] = this.firmwareTarget ?? 0; + // 32 seems like a reasonable default fragment size, + // but it should be specified anyways + this.payload.writeUInt16BE(this.fragmentSize ?? 32, 7); + this.payload[9] = (this.activation ? 0b1 : 0) + | (this.nonSecureTransfer ? 0b10 : 0) + | (this.resume ? 0b100 : 0); + this.payload[10] = this.hardwareVersion ?? 0x00; + + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "manufacturer id": num2hex(this.manufacturerId), "firmware id": num2hex(this.firmwareId), @@ -661,7 +627,7 @@ export class FirmwareUpdateMetaDataCCRequestGet message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -671,10 +637,9 @@ export class FirmwareUpdateMetaDataCCRequestGet // This is sent to us from the node, so we expect no response export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 3); this.numReports = this.payload[0]; this.reportNumber = this.payload.readUInt16BE(1) & 0x7fff; @@ -683,9 +648,9 @@ export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { public readonly numReports: number; public readonly reportNumber: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "total # of reports": this.numReports, "report number": this.reportNumber, @@ -707,12 +672,11 @@ export interface FirmwareUpdateMetaDataCCReportOptions // We send this in reply to the Get command and expect no response export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | FirmwareUpdateMetaDataCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -730,7 +694,7 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { public reportNumber: number; public firmwareData: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const commandBuffer = Buffer.concat([ Buffer.allocUnsafe(2), // placeholder for report number this.firmwareData, @@ -740,7 +704,10 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { 0, ); - if (this.version >= 2) { + // V1 devices would consider the checksum to be part of the firmware data + // so it must not be included for those + const ccVersion = getEffectiveCCVersion(ctx, this); + if (ccVersion >= 2) { // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation let crc = CRC16_CCITT(Buffer.from([this.ccId, this.ccCommand])); @@ -754,12 +721,12 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { this.payload = commandBuffer; } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "report #": this.reportNumber, "is last": this.isLast, @@ -773,10 +740,9 @@ export class FirmwareUpdateMetaDataCCStatusReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.status = this.payload[0]; if (this.payload.length >= 3) { @@ -788,7 +754,7 @@ export class FirmwareUpdateMetaDataCCStatusReport /** The wait time in seconds before the node becomes available for communication after the update */ public readonly waitTime?: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { status: getEnumMemberName(FirmwareUpdateStatus, this.status), }; @@ -796,7 +762,7 @@ export class FirmwareUpdateMetaDataCCStatusReport message["wait time"] = `${this.waitTime} seconds`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -807,17 +773,17 @@ export class FirmwareUpdateMetaDataCCActivationReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 8); this.manufacturerId = this.payload.readUInt16BE(0); this.firmwareId = this.payload.readUInt16BE(2); this.checksum = this.payload.readUInt16BE(4); this.firmwareTarget = this.payload[6]; this.activationStatus = this.payload[7]; - if (this.version >= 5 && this.payload.length >= 9) { + if (this.payload.length >= 9) { + // V5+ this.hardwareVersion = this.payload[8]; } } @@ -829,7 +795,7 @@ export class FirmwareUpdateMetaDataCCActivationReport public readonly activationStatus: FirmwareUpdateActivationStatus; public readonly hardwareVersion?: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "manufacturer id": num2hex(this.manufacturerId), "firmware id": num2hex(this.firmwareId), @@ -844,7 +810,7 @@ export class FirmwareUpdateMetaDataCCActivationReport message.hardwareVersion = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -866,12 +832,11 @@ export class FirmwareUpdateMetaDataCCActivationSet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (FirmwareUpdateMetaDataCCActivationSetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -893,20 +858,17 @@ export class FirmwareUpdateMetaDataCCActivationSet public firmwareTarget: number; public hardwareVersion?: number; - public serialize(): Buffer { - const isV5 = this.version >= 5 && this.hardwareVersion != undefined; - this.payload = Buffer.allocUnsafe(7 + (isV5 ? 1 : 0)); + public serialize(ctx: CCEncodingContext): Buffer { + this.payload = Buffer.allocUnsafe(8); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload.writeUInt16BE(this.checksum, 4); this.payload[6] = this.firmwareTarget; - if (isV5) { - this.payload[7] = this.hardwareVersion!; - } - return super.serialize(); + this.payload[7] = this.hardwareVersion ?? 0x00; + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "manufacturer id": num2hex(this.manufacturerId), "firmware id": num2hex(this.firmwareId), @@ -917,7 +879,7 @@ export class FirmwareUpdateMetaDataCCActivationSet message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -928,10 +890,9 @@ export class FirmwareUpdateMetaDataCCPrepareReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 3); this.status = this.payload[0]; this.checksum = this.payload.readUInt16BE(1); @@ -940,9 +901,9 @@ export class FirmwareUpdateMetaDataCCPrepareReport public readonly status: FirmwareDownloadStatus; public readonly checksum: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { status: getEnumMemberName(FirmwareDownloadStatus, this.status), checksum: num2hex(this.checksum), @@ -968,12 +929,11 @@ export class FirmwareUpdateMetaDataCCPrepareGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | FirmwareUpdateMetaDataCCPrepareGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -995,19 +955,19 @@ export class FirmwareUpdateMetaDataCCPrepareGet public fragmentSize: number; public hardwareVersion: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(8); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.firmwareId, 2); this.payload[4] = this.firmwareTarget; this.payload.writeUInt16BE(this.fragmentSize, 5); this.payload[7] = this.hardwareVersion; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "manufacturer id": num2hex(this.manufacturerId), "firmware id": num2hex(this.firmwareId), diff --git a/packages/cc/src/cc/HumidityControlModeCC.ts b/packages/cc/src/cc/HumidityControlModeCC.ts index b096e6617b9a..940c1735315c 100644 --- a/packages/cc/src/cc/HumidityControlModeCC.ts +++ b/packages/cc/src/cc/HumidityControlModeCC.ts @@ -12,11 +12,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -32,6 +28,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -125,11 +124,11 @@ export class HumidityControlModeCCAPI extends CCAPI { HumidityControlModeCommand.Get, ); - const cc = new HumidityControlModeCCGet(this.applHost, { + const cc = new HumidityControlModeCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlModeCCReport >( cc, @@ -149,12 +148,12 @@ export class HumidityControlModeCCAPI extends CCAPI { HumidityControlModeCommand.Set, ); - const cc = new HumidityControlModeCCSet(this.applHost, { + const cc = new HumidityControlModeCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, mode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupportedModes(): Promise< @@ -165,11 +164,11 @@ export class HumidityControlModeCCAPI extends CCAPI { HumidityControlModeCommand.SupportedGet, ); - const cc = new HumidityControlModeCCSupportedGet(this.applHost, { + const cc = new HumidityControlModeCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlModeCCSupportedReport >( cc, @@ -185,25 +184,27 @@ export class HumidityControlModeCCAPI extends CCAPI { export class HumidityControlModeCC extends CommandClass { declare ccCommand: HumidityControlModeCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Humidity Control Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // First query the possible modes to set the metadata - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported humidity control modes...", direction: "outbound", @@ -221,13 +222,13 @@ export class HumidityControlModeCC extends CommandClass { ) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported humidity control modes timed out, skipping interview...", @@ -236,32 +237,34 @@ export class HumidityControlModeCC extends CommandClass { return; } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Humidity Control Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current humidity control mode...", direction: "outbound", }); const currentMode = await api.get(); if (currentMode) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "received current humidity control mode: " + getEnumMemberName(HumidityControlMode, currentMode), @@ -280,12 +283,11 @@ export interface HumidityControlModeCCSetOptions extends CCCommandOptions { @useSupervision() export class HumidityControlModeCCSet extends HumidityControlModeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | HumidityControlModeCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -299,14 +301,14 @@ export class HumidityControlModeCCSet extends HumidityControlModeCC { public mode: HumidityControlMode; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.mode & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { mode: getEnumMemberName(HumidityControlMode, this.mode), }, @@ -317,10 +319,9 @@ export class HumidityControlModeCCSet extends HumidityControlModeCC { @CCCommand(HumidityControlModeCommand.Report) export class HumidityControlModeCCReport extends HumidityControlModeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.mode = this.payload[0] & 0b1111; @@ -329,9 +330,9 @@ export class HumidityControlModeCCReport extends HumidityControlModeCC { @ccValue(HumidityControlModeCCValues.mode) public readonly mode: HumidityControlMode; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { mode: getEnumMemberName(HumidityControlMode, this.mode), }, @@ -348,10 +349,9 @@ export class HumidityControlModeCCSupportedReport extends HumidityControlModeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this._supportedModes = parseBitMask( @@ -364,12 +364,12 @@ export class HumidityControlModeCCSupportedReport } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Use this information to create the metadata for the mode property const modeValue = HumidityControlModeCCValues.mode; - this.setMetadata(applHost, modeValue, { + this.setMetadata(ctx, modeValue, { ...modeValue.meta, states: enumValuesToMetadataStates( HumidityControlMode, @@ -386,9 +386,9 @@ export class HumidityControlModeCCSupportedReport return this._supportedModes; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported modes": this.supportedModes .map( diff --git a/packages/cc/src/cc/HumidityControlOperatingStateCC.ts b/packages/cc/src/cc/HumidityControlOperatingStateCC.ts index b04db8898b98..95cd60109923 100644 --- a/packages/cc/src/cc/HumidityControlOperatingStateCC.ts +++ b/packages/cc/src/cc/HumidityControlOperatingStateCC.ts @@ -7,11 +7,7 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -22,6 +18,8 @@ import { import { CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -89,11 +87,11 @@ export class HumidityControlOperatingStateCCAPI extends CCAPI { HumidityControlOperatingStateCommand.Get, ); - const cc = new HumidityControlOperatingStateCCGet(this.applHost, { + const cc = new HumidityControlOperatingStateCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlOperatingStateCCReport >( cc, @@ -111,41 +109,45 @@ export class HumidityControlOperatingStateCCAPI extends CCAPI { export class HumidityControlOperatingStateCC extends CommandClass { declare ccCommand: HumidityControlOperatingStateCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Humidity Control Operating State"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current humidity control operating state...", direction: "outbound", }); const currentStatus = await api.get(); if (currentStatus) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "received current humidity control operating state: " + getEnumMemberName( @@ -163,10 +165,9 @@ export class HumidityControlOperatingStateCCReport extends HumidityControlOperatingStateCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.state = this.payload[0] & 0b1111; @@ -175,9 +176,9 @@ export class HumidityControlOperatingStateCCReport @ccValue(HumidityControlOperatingStateCCValues.state) public readonly state: HumidityControlOperatingState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { state: getEnumMemberName( HumidityControlOperatingState, diff --git a/packages/cc/src/cc/HumidityControlSetpointCC.ts b/packages/cc/src/cc/HumidityControlSetpointCC.ts index 2705a39ecb22..82eb4d7d199c 100644 --- a/packages/cc/src/cc/HumidityControlSetpointCC.ts +++ b/packages/cc/src/cc/HumidityControlSetpointCC.ts @@ -17,11 +17,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -37,6 +33,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -206,12 +205,12 @@ export class HumidityControlSetpointCCAPI extends CCAPI { HumidityControlSetpointCommand.Get, ); - const cc = new HumidityControlSetpointCCGet(this.applHost, { + const cc = new HumidityControlSetpointCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCReport >( cc, @@ -239,14 +238,14 @@ export class HumidityControlSetpointCCAPI extends CCAPI { HumidityControlSetpointCommand.Set, ); - const cc = new HumidityControlSetpointCCSet(this.applHost, { + const cc = new HumidityControlSetpointCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, value, scale, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -258,12 +257,12 @@ export class HumidityControlSetpointCCAPI extends CCAPI { HumidityControlSetpointCommand.CapabilitiesGet, ); - const cc = new HumidityControlSetpointCCCapabilitiesGet(this.applHost, { + const cc = new HumidityControlSetpointCCCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCCapabilitiesReport >( cc, @@ -287,11 +286,11 @@ export class HumidityControlSetpointCCAPI extends CCAPI { HumidityControlSetpointCommand.SupportedGet, ); - const cc = new HumidityControlSetpointCCSupportedGet(this.applHost, { + const cc = new HumidityControlSetpointCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCSupportedReport >( cc, @@ -309,15 +308,12 @@ export class HumidityControlSetpointCCAPI extends CCAPI { HumidityControlSetpointCommand.SupportedGet, ); - const cc = new HumidityControlSetpointCCScaleSupportedGet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - setpointType, - }, - ); - const response = await this.applHost.sendCommand< + const cc = new HumidityControlSetpointCCScaleSupportedGet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + setpointType, + }); + const response = await this.host.sendCommand< HumidityControlSetpointCCScaleSupportedReport >( cc, @@ -336,7 +332,7 @@ export class HumidityControlSetpointCC extends CommandClass { declare ccCommand: HumidityControlSetpointCommand; public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -346,22 +342,24 @@ export class HumidityControlSetpointCC extends CommandClass { propertyKey as any, ); } else { - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Humidity Control Setpoint"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -369,7 +367,7 @@ export class HumidityControlSetpointCC extends CommandClass { // Query the supported setpoint types let setpointTypes: HumidityControlSetpointType[] = []; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving supported setpoint types...", direction: "outbound", @@ -385,13 +383,13 @@ export class HumidityControlSetpointCC extends CommandClass { .map((name) => `· ${name}`) .join("\n"); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported setpoint types timed out, skipping interview...", @@ -406,7 +404,7 @@ export class HumidityControlSetpointCC extends CommandClass { type, ); // Find out the capabilities of this setpoint - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `retrieving capabilities for setpoint ${setpointName}...`, @@ -421,7 +419,7 @@ ${ .map((t) => `\n· ${t.key} ${t.unit} - ${t.label}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -433,7 +431,7 @@ ${ for (const scale of setpointScaleSupported) { if (scale.unit) states[scale.key] = scale.unit; } - this.setMetadata(applHost, scaleValue, { + this.setMetadata(ctx, scaleValue, { ...scaleValue.meta, states, }); @@ -450,7 +448,7 @@ ${ `received capabilities for setpoint ${setpointName}: minimum value: ${setpointCaps.minValue} ${minValueUnit} maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -459,25 +457,27 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; } // Query the current value for all setpoint types - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Humidity Control Setpoint"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const setpointTypes: HumidityControlSetpointType[] = this.getValue( - applHost, + ctx, HumidityControlSetpointCCValues.supportedSetpointTypes, ) ?? []; @@ -488,7 +488,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; type, ); // Every time, query the current value - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current value of setpoint ${setpointName}...`, @@ -500,7 +500,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; `received current value of setpoint ${setpointName}: ${setpoint.value} ${ getScale(setpoint.scale).unit ?? "" }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -521,12 +521,11 @@ export interface HumidityControlSetpointCCSetOptions extends CCCommandOptions { @useSupervision() export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | HumidityControlSetpointCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -544,18 +543,18 @@ export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { public value: number; public scale: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.setpointType & 0b1111]), encodeFloatWithScale(this.value, this.scale), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const scale = getScale(this.scale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, @@ -570,10 +569,9 @@ export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { @CCCommand(HumidityControlSetpointCommand.Report) export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this._type = this.payload[0] & 0b1111; @@ -591,8 +589,8 @@ export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { this.scale = scale; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const scale = getScale(this.scale); @@ -600,22 +598,22 @@ export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { this.type, ); const existingMetadata = this.getMetadata( - applHost, + ctx, setpointValue, ); // Update the metadata when it is missing or the unit has changed if (existingMetadata?.unit !== scale.unit) { - this.setMetadata(applHost, setpointValue, { + this.setMetadata(ctx, setpointValue, { ...(existingMetadata ?? setpointValue.meta), unit: scale.unit, }); } - this.setValue(applHost, setpointValue, this._value); + this.setValue(ctx, setpointValue, this._value); // Remember the device-preferred setpoint scale so it can be used in SET commands this.setValue( - applHost, + ctx, HumidityControlSetpointCCValues.setpointScale(this.type), this.scale, ); @@ -634,10 +632,10 @@ export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { return this._value; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const scale = getScale(this.scale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, @@ -669,12 +667,11 @@ export interface HumidityControlSetpointCCGetOptions extends CCCommandOptions { ) export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | HumidityControlSetpointCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -688,14 +685,14 @@ export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { public setpointType: HumidityControlSetpointType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.setpointType & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, @@ -711,10 +708,9 @@ export class HumidityControlSetpointCCSupportedReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.supportedSetpointTypes = parseBitMask( @@ -727,9 +723,9 @@ export class HumidityControlSetpointCCSupportedReport public readonly supportedSetpointTypes: readonly HumidityControlSetpointType[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported setpoint types": this.supportedSetpointTypes .map( @@ -758,10 +754,9 @@ export class HumidityControlSetpointCCScaleSupportedReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); @@ -773,12 +768,12 @@ export class HumidityControlSetpointCCScaleSupportedReport public readonly supportedScales: readonly number[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const supportedScales = this.supportedScales.map((scale) => getScale(scale) ); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "scale supported": supportedScales .map((t) => `\n· ${t.key} ${t.unit} - ${t.label}`) @@ -801,12 +796,11 @@ export class HumidityControlSetpointCCScaleSupportedGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | HumidityControlSetpointCCScaleSupportedGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -820,14 +814,14 @@ export class HumidityControlSetpointCCScaleSupportedGet public setpointType: HumidityControlSetpointType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.setpointType & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, @@ -843,10 +837,9 @@ export class HumidityControlSetpointCCCapabilitiesReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this._type = this.payload[0] & 0b1111; @@ -861,14 +854,14 @@ export class HumidityControlSetpointCCCapabilitiesReport parseFloatWithScale(this.payload.subarray(1 + bytesRead))); } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Predefine the metadata const setpointValue = HumidityControlSetpointCCValues.setpoint( this.type, ); - this.setMetadata(applHost, setpointValue, { + this.setMetadata(ctx, setpointValue, { ...setpointValue.meta, min: this._minValue, max: this._maxValue, @@ -904,11 +897,11 @@ export class HumidityControlSetpointCCCapabilitiesReport return this._maxValueScale; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const minValueScale = getScale(this.minValueScale); const maxValueScale = getScale(this.maxValueScale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, @@ -934,12 +927,11 @@ export class HumidityControlSetpointCCCapabilitiesGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | HumidityControlSetpointCCCapabilitiesGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -953,14 +945,14 @@ export class HumidityControlSetpointCCCapabilitiesGet public setpointType: HumidityControlSetpointType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.setpointType & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( HumidityControlSetpointType, diff --git a/packages/cc/src/cc/InclusionControllerCC.ts b/packages/cc/src/cc/InclusionControllerCC.ts index 5efbd3665974..ba26ef111bcc 100644 --- a/packages/cc/src/cc/InclusionControllerCC.ts +++ b/packages/cc/src/cc/InclusionControllerCC.ts @@ -4,7 +4,7 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host"; import { getEnumMemberName } from "@zwave-js/shared"; import { CCAPI } from "../lib/API"; import { @@ -57,13 +57,13 @@ export class InclusionControllerCCAPI extends CCAPI { InclusionControllerCommand.Initiate, ); - const cc = new InclusionControllerCCInitiate(this.applHost, { + const cc = new InclusionControllerCCInitiate({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, includedNodeId: nodeId, step, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** Indicate to the other node that the given inclusion step has been completed */ @@ -76,13 +76,13 @@ export class InclusionControllerCCAPI extends CCAPI { InclusionControllerCommand.Complete, ); - const cc = new InclusionControllerCCComplete(this.applHost, { + const cc = new InclusionControllerCCComplete({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, step, status, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -95,12 +95,11 @@ export interface InclusionControllerCCCompleteOptions extends CCCommandOptions { @CCCommand(InclusionControllerCommand.Complete) export class InclusionControllerCCComplete extends InclusionControllerCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | InclusionControllerCCCompleteOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.step = this.payload[0]; @@ -117,14 +116,14 @@ export class InclusionControllerCCComplete extends InclusionControllerCC { public step: InclusionControllerStep; public status: InclusionControllerStatus; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.step, this.status]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { step: getEnumMemberName(InclusionControllerStep, this.step), status: getEnumMemberName( @@ -145,12 +144,11 @@ export interface InclusionControllerCCInitiateOptions extends CCCommandOptions { @CCCommand(InclusionControllerCommand.Initiate) export class InclusionControllerCCInitiate extends InclusionControllerCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | InclusionControllerCCInitiateOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.includedNodeId = this.payload[0]; @@ -167,14 +165,14 @@ export class InclusionControllerCCInitiate extends InclusionControllerCC { public includedNodeId: number; public step: InclusionControllerStep; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.includedNodeId, this.step]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "included node id": this.includedNodeId, step: getEnumMemberName(InclusionControllerStep, this.step), diff --git a/packages/cc/src/cc/IndicatorCC.ts b/packages/cc/src/cc/IndicatorCC.ts index 15387e8f8278..8e8e75e03d13 100644 --- a/packages/cc/src/cc/IndicatorCC.ts +++ b/packages/cc/src/cc/IndicatorCC.ts @@ -1,7 +1,6 @@ -import type { ConfigManager } from "@zwave-js/config"; import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, Indicator, type MaybeNotKnown, type MessageOrCCLogEntry, @@ -16,11 +15,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp, roundTo } from "alcalzone-shared/math"; @@ -38,6 +33,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -202,7 +200,6 @@ export const IndicatorCCValues = Object.freeze({ * Looks up the configured metadata for the given indicator and property */ function getIndicatorMetadata( - configManager: ConfigManager, indicatorId: Indicator, propertyId: number, overrideIndicatorLabel?: string, @@ -306,7 +303,6 @@ export class IndicatorCCAPI extends CCAPI { const indicatorId = property; const propertyId = propertyKey; const expectedType = getIndicatorMetadata( - this.applHost.configManager, indicatorId, propertyId, ).type as "number" | "boolean"; @@ -360,12 +356,12 @@ export class IndicatorCCAPI extends CCAPI { ): Promise> { this.assertSupportsCommand(IndicatorCommand, IndicatorCommand.Get); - const cc = new IndicatorCCGet(this.applHost, { + const cc = new IndicatorCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -380,12 +376,29 @@ export class IndicatorCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(IndicatorCommand, IndicatorCommand.Set); - const cc = new IndicatorCCSet(this.applHost, { + if (this.version === 1 && typeof value !== "number") { + throw new ZWaveError( + `Node ${this.endpoint + .nodeId as number} only supports IndicatorCC V1 which requires a single value to be set`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if ( + this.version >= 2 + && isArray(value) + && value.length > MAX_INDICATOR_OBJECTS + ) { + throw new ZWaveError( + `Only ${MAX_INDICATOR_OBJECTS} indicator values can be set at a time!`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + const cc = new IndicatorCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...(typeof value === "number" ? { value } : { values: value }), }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -397,13 +410,13 @@ export class IndicatorCCAPI extends CCAPI { IndicatorCommand.Report, ); - const cc = new IndicatorCCReport(this.applHost, { + const cc = new IndicatorCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -420,12 +433,12 @@ export class IndicatorCCAPI extends CCAPI { IndicatorCommand.SupportedGet, ); - const cc = new IndicatorCCSupportedGet(this.applHost, { + const cc = new IndicatorCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IndicatorCCSupportedReport >( cc, @@ -454,7 +467,7 @@ export class IndicatorCCAPI extends CCAPI { IndicatorCommand.SupportedReport, ); - const cc = new IndicatorCCSupportedReport(this.applHost, { + const cc = new IndicatorCCSupportedReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, indicatorId, @@ -462,7 +475,7 @@ export class IndicatorCCAPI extends CCAPI { nextIndicatorId, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -475,14 +488,14 @@ export class IndicatorCCAPI extends CCAPI { IndicatorCommand.DescriptionReport, ); - const cc = new IndicatorCCDescriptionReport(this.applHost, { + const cc = new IndicatorCCDescriptionReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, indicatorId, description, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -551,7 +564,7 @@ export class IndicatorCCAPI extends CCAPI { } const supportedPropertyIDs = IndicatorCC.getSupportedPropertyIDsCached( - this.applHost, + this.host, this.endpoint, indicatorId, ); @@ -655,12 +668,12 @@ export class IndicatorCCAPI extends CCAPI { IndicatorCommand.DescriptionGet, ); - const cc = new IndicatorCCDescriptionGet(this.applHost, { + const cc = new IndicatorCCDescriptionGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IndicatorCCDescriptionReport >( cc, @@ -676,25 +689,27 @@ export class IndicatorCCAPI extends CCAPI { export class IndicatorCC extends CommandClass { declare ccCommand: IndicatorCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Indicator, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (this.version > 1) { - applHost.controllerLog.logNode(node.id, { + if (api.version > 1) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "scanning supported indicator IDs...", direction: "outbound", @@ -705,7 +720,7 @@ export class IndicatorCC extends CommandClass { do { const supportedResponse = await api.getSupported(curId); if (!supportedResponse) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Time out while scanning supported indicator IDs, skipping interview...", @@ -722,7 +737,7 @@ export class IndicatorCC extends CommandClass { // The IDs are not stored by the report CCs so store them here once we have all of them this.setValue( - applHost, + ctx, IndicatorCCValues.supportedIndicatorIds, supportedIndicatorIds, ); @@ -731,17 +746,17 @@ export class IndicatorCC extends CommandClass { ", ", ) }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); - if (this.version >= 4) { + if (api.version >= 4) { const manufacturerDefinedIndicatorIds = supportedIndicatorIds .filter((id) => isManufacturerDefinedIndicator(id)); if (manufacturerDefinedIndicatorIds.length > 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving description for manufacturer-defined indicator IDs...", @@ -756,25 +771,27 @@ export class IndicatorCC extends CommandClass { } // Query current values - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Indicator, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - if (this.version === 1) { - applHost.controllerLog.logNode(node.id, { + if (api.version === 1) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting current indicator value...", direction: "outbound", @@ -782,11 +799,11 @@ export class IndicatorCC extends CommandClass { await api.get(); } else { const supportedIndicatorIds: number[] = this.getValue( - applHost, + ctx, IndicatorCCValues.supportedIndicatorIds, ) ?? []; for (const indicatorId of supportedIndicatorIds) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `requesting current indicator value (id = ${ num2hex( @@ -801,7 +818,7 @@ export class IndicatorCC extends CommandClass { } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -816,11 +833,11 @@ export class IndicatorCC extends CommandClass { const prop = getIndicatorProperty(propertyKey); if (prop) return prop.label; } - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } public translateProperty( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey?: string | number, ): string { @@ -828,13 +845,13 @@ export class IndicatorCC extends CommandClass { // The indicator corresponds to our property if (property in Indicator) return Indicator[property]; } - return super.translateProperty(applHost, property, propertyKey); + return super.translateProperty(ctx, property, propertyKey); } - protected supportsV2Indicators(applHost: ZWaveApplicationHost): boolean { + protected supportsV2Indicators(ctx: GetValueDB): boolean { // First test if there are any indicator ids defined const supportedIndicatorIds = this.getValue( - applHost, + ctx, IndicatorCCValues.supportedIndicatorIds, ); if (!supportedIndicatorIds?.length) return false; @@ -842,18 +859,18 @@ export class IndicatorCC extends CommandClass { return supportedIndicatorIds.some( (indicatorId) => !!this.getValue( - applHost, + ctx, IndicatorCCValues.supportedPropertyIDs(indicatorId), )?.length, ); } public static getSupportedPropertyIDsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, indicatorId: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( IndicatorCCValues.supportedPropertyIDs(indicatorId).endpoint( @@ -882,12 +899,11 @@ export type IndicatorCCSetOptions = @useSupervision() export class IndicatorCCSet extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (IndicatorCCSetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -910,27 +926,10 @@ export class IndicatorCCSet extends IndicatorCC { } } } else { - if (this.version === 1) { - if (!("value" in options)) { - throw new ZWaveError( - `Node ${this - .nodeId as number} only supports IndicatorCC V1 which requires a single value to be set`, - ZWaveErrorCodes.Argument_Invalid, - ); - } + if ("value" in options) { this.indicator0Value = options.value; } else { - if ("value" in options) { - this.indicator0Value = options.value; - } else { - if (options.values.length > MAX_INDICATOR_OBJECTS) { - throw new ZWaveError( - `Only ${MAX_INDICATOR_OBJECTS} indicator values can be set at a time!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.values = options.values; - } + this.values = options.values; } } } @@ -938,7 +937,7 @@ export class IndicatorCCSet extends IndicatorCC { public indicator0Value: number | undefined; public values: IndicatorObject[] | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (this.values != undefined) { // V2+ this.payload = Buffer.alloc(2 + 3 * this.values.length, 0); @@ -960,10 +959,10 @@ export class IndicatorCCSet extends IndicatorCC { // V1 this.payload = Buffer.from([this.indicator0Value ?? 0]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.indicator0Value != undefined) { message["indicator 0 value"] = this.indicator0Value; @@ -981,7 +980,7 @@ export class IndicatorCCSet extends IndicatorCC { }`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -999,12 +998,11 @@ export type IndicatorCCReportSpecificOptions = @CCCommand(IndicatorCommand.Report) export class IndicatorCCReport extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (IndicatorCCReportSpecificOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -1068,19 +1066,19 @@ export class IndicatorCCReport extends IndicatorCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; if (this.indicator0Value != undefined) { - if (!this.supportsV2Indicators(applHost)) { + if (!this.supportsV2Indicators(ctx)) { // Publish the value const valueV1 = IndicatorCCValues.valueV1; - this.setMetadata(applHost, valueV1); - this.setValue(applHost, valueV1, this.indicator0Value); + this.setMetadata(ctx, valueV1); + this.setValue(ctx, valueV1, this.indicator0Value); } else { if (this.isSinglecast()) { // Don't! - applHost.controllerLog.logNode(this.nodeId, { + ctx.logNode(this.nodeId, { message: `ignoring V1 indicator report because the node supports V2 indicators`, direction: "none", @@ -1091,7 +1089,7 @@ export class IndicatorCCReport extends IndicatorCC { } else if (this.values) { // Store the simple values first for (const value of this.values) { - this.setIndicatorValue(applHost, value); + this.setIndicatorValue(ctx, value); } // Then group values into the convenience properties @@ -1105,7 +1103,7 @@ export class IndicatorCCReport extends IndicatorCC { if (timeout?.seconds) timeoutString += `${timeout.seconds}s`; this.setValue( - applHost, + ctx, IndicatorCCValues.timeout, timeoutString, ); @@ -1119,7 +1117,7 @@ export class IndicatorCCReport extends IndicatorCC { public readonly values: IndicatorObject[] | undefined; private setIndicatorValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, value: IndicatorObject, ): void { // Manufacturer-defined indicators may need a custom label @@ -1127,13 +1125,12 @@ export class IndicatorCCReport extends IndicatorCC { value.indicatorId, ) ? this.getValue( - applHost, + ctx, IndicatorCCValues.indicatorDescription(value.indicatorId), ) : undefined; const metadata = getIndicatorMetadata( - applHost.configManager, value.indicatorId, value.propertyId, overrideIndicatorLabel, @@ -1148,11 +1145,11 @@ export class IndicatorCCReport extends IndicatorCC { value.indicatorId, value.propertyId, ); - this.setMetadata(applHost, valueV2, metadata); - this.setValue(applHost, valueV2, value.value); + this.setMetadata(ctx, valueV2, metadata); + this.setValue(ctx, valueV2, value.value); } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (this.values != undefined) { // V2+ this.payload = Buffer.alloc(2 + 3 * this.values.length, 0); @@ -1174,10 +1171,10 @@ export class IndicatorCCReport extends IndicatorCC { // V1 this.payload = Buffer.from([this.indicator0Value ?? 0]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.indicator0Value != undefined) { message["indicator 0 value"] = this.indicator0Value; @@ -1195,7 +1192,7 @@ export class IndicatorCCReport extends IndicatorCC { }`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1210,10 +1207,9 @@ export interface IndicatorCCGetOptions extends CCCommandOptions { @expectedCCResponse(IndicatorCCReport) export class IndicatorCCGet extends IndicatorCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | IndicatorCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length > 0) { this.indicatorId = this.payload[0]; @@ -1225,16 +1221,16 @@ export class IndicatorCCGet extends IndicatorCC { public indicatorId: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (this.indicatorId != undefined) { this.payload = Buffer.from([this.indicatorId]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { indicator: getIndicatorName(this.indicatorId), }, @@ -1252,12 +1248,11 @@ export interface IndicatorCCSupportedReportOptions extends CCCommandOptions { @CCCommand(IndicatorCommand.SupportedReport) export class IndicatorCCSupportedReport extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IndicatorCCSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -1281,13 +1276,13 @@ export class IndicatorCCSupportedReport extends IndicatorCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; if (this.indicatorId !== 0x00) { // Remember which property IDs are supported this.setValue( - applHost, + ctx, IndicatorCCValues.supportedPropertyIDs(this.indicatorId), this.supportedProperties, ); @@ -1299,7 +1294,7 @@ export class IndicatorCCSupportedReport extends IndicatorCC { public readonly nextIndicatorId: number; public readonly supportedProperties: readonly number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const bitmask = this.supportedProperties.length > 0 ? encodeBitMask(this.supportedProperties, undefined, 0) : Buffer.from([]); @@ -1312,12 +1307,12 @@ export class IndicatorCCSupportedReport extends IndicatorCC { bitmask, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { indicator: getIndicatorName(this.indicatorId), "supported properties": `${ @@ -1354,12 +1349,11 @@ function testResponseForIndicatorSupportedGet( ) export class IndicatorCCSupportedGet extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IndicatorCCSupportedGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.indicatorId = this.payload[0]; @@ -1370,14 +1364,14 @@ export class IndicatorCCSupportedGet extends IndicatorCC { public indicatorId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.indicatorId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { indicator: getIndicatorName(this.indicatorId), }, @@ -1394,12 +1388,11 @@ export interface IndicatorCCDescriptionReportOptions { @CCCommand(IndicatorCommand.DescriptionReport) export class IndicatorCCDescriptionReport extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (IndicatorCCDescriptionReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -1418,12 +1411,12 @@ export class IndicatorCCDescriptionReport extends IndicatorCC { public indicatorId: number; public description: string; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; if (this.description) { this.setValue( - applHost, + ctx, IndicatorCCValues.indicatorDescription(this.indicatorId), this.description, ); @@ -1432,18 +1425,18 @@ export class IndicatorCCDescriptionReport extends IndicatorCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const description = Buffer.from(this.description, "utf8"); this.payload = Buffer.concat([ Buffer.from([this.indicatorId, description.length]), description, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "indicator ID": this.indicatorId, description: this.description || "(none)", @@ -1471,12 +1464,11 @@ function testResponseForIndicatorDescriptionGet( ) export class IndicatorCCDescriptionGet extends IndicatorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IndicatorCCDescriptionGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.indicatorId = this.payload[0]; @@ -1493,14 +1485,14 @@ export class IndicatorCCDescriptionGet extends IndicatorCC { public indicatorId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.indicatorId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "indicator ID": this.indicatorId, }, diff --git a/packages/cc/src/cc/IrrigationCC.ts b/packages/cc/src/cc/IrrigationCC.ts index 3d880b26e894..5a58c8e1e4b4 100644 --- a/packages/cc/src/cc/IrrigationCC.ts +++ b/packages/cc/src/cc/IrrigationCC.ts @@ -1,6 +1,6 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -15,11 +15,7 @@ import { parseFloatWithScale, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -38,6 +34,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -602,11 +601,11 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.SystemInfoGet, ); - const cc = new IrrigationCCSystemInfoGet(this.applHost, { + const cc = new IrrigationCCSystemInfoGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemInfoReport >( cc, @@ -629,11 +628,11 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.SystemStatusGet, ); - const cc = new IrrigationCCSystemStatusGet(this.applHost, { + const cc = new IrrigationCCSystemStatusGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemStatusReport >( cc, @@ -667,11 +666,11 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.SystemConfigGet, ); - const cc = new IrrigationCCSystemConfigGet(this.applHost, { + const cc = new IrrigationCCSystemConfigGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemConfigReport >( cc, @@ -697,13 +696,13 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.SystemConfigSet, ); - const cc = new IrrigationCCSystemConfigSet(this.applHost, { + const cc = new IrrigationCCSystemConfigSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...config, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -714,12 +713,12 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveInfoGet, ); - const cc = new IrrigationCCValveInfoGet(this.applHost, { + const cc = new IrrigationCCValveInfoGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, valveId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveInfoReport >( cc, @@ -748,13 +747,13 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveConfigSet, ); - const cc = new IrrigationCCValveConfigSet(this.applHost, { + const cc = new IrrigationCCValveConfigSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -765,12 +764,12 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveConfigGet, ); - const cc = new IrrigationCCValveConfigGet(this.applHost, { + const cc = new IrrigationCCValveConfigGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, valveId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveConfigReport >( cc, @@ -799,14 +798,14 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveRun, ); - const cc = new IrrigationCCValveRun(this.applHost, { + const cc = new IrrigationCCValveRun({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, valveId, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -828,7 +827,7 @@ export class IrrigationCCAPI extends CCAPI { if (!this.endpoint.virtual) { const maxValveTableSize = IrrigationCC.getMaxValveTableSizeCached( - this.applHost, + this.host, this.endpoint, ); if ( @@ -842,14 +841,14 @@ export class IrrigationCCAPI extends CCAPI { } } - const cc = new IrrigationCCValveTableSet(this.applHost, { + const cc = new IrrigationCCValveTableSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, tableId, entries, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -861,12 +860,12 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveTableGet, ); - const cc = new IrrigationCCValveTableGet(this.applHost, { + const cc = new IrrigationCCValveTableGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, tableId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveTableReport >( cc, @@ -886,13 +885,13 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.ValveTableRun, ); - const cc = new IrrigationCCValveTableRun(this.applHost, { + const cc = new IrrigationCCValveTableRun({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, tableIDs, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -908,13 +907,13 @@ export class IrrigationCCAPI extends CCAPI { IrrigationCommand.SystemShutoff, ); - const cc = new IrrigationCCSystemShutoff(this.applHost, { + const cc = new IrrigationCCSystemShutoff({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** Shuts off the entire system permanently and prevents schedules from running */ @@ -1118,10 +1117,10 @@ export class IrrigationCC extends CommandClass { * This only works AFTER the node has been interviewed. */ public static getMaxValveTableSizeCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( IrrigationCCValues.maxValveTableSize.endpoint(endpoint.index), @@ -1133,10 +1132,10 @@ export class IrrigationCC extends CommandClass { * This only works AFTER the node has been interviewed. */ public static getNumValvesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue(IrrigationCCValues.numValves.endpoint(endpoint.index)); } @@ -1146,41 +1145,43 @@ export class IrrigationCC extends CommandClass { * This only works AFTER the node has been interviewed. */ public static supportsMasterValveCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - return !!applHost + return !!ctx .getValueDB(endpoint.nodeId) .getValue( IrrigationCCValues.supportsMasterValve.endpoint(endpoint.index), ); } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Irrigation, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying irrigation system info...", direction: "outbound", }); const systemInfo = await api.getSystemInfo(); if (!systemInfo) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Time out while querying irrigation system info, skipping interview...", @@ -1193,7 +1194,7 @@ supports master valve: ${systemInfo.supportsMasterValve} no. of valves: ${systemInfo.numValves} no. of valve tables: ${systemInfo.numValveTables} max. valve table size: ${systemInfo.maxValveTableSize}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -1202,37 +1203,39 @@ max. valve table size: ${systemInfo.maxValveTableSize}`; // For each valve, create the values to start/stop a run for (let i = 1; i <= systemInfo.numValves; i++) { this.ensureMetadata( - applHost, + ctx, IrrigationCCValues.valveRunDuration(i), ); this.ensureMetadata( - applHost, + ctx, IrrigationCCValues.valveRunStartStop(i), ); } // And create a shutoff value - this.ensureMetadata(applHost, IrrigationCCValues.shutoffSystem); + this.ensureMetadata(ctx, IrrigationCCValues.shutoffSystem); // Query current values - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Irrigation, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current system config - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying irrigation system configuration...", direction: "outbound", @@ -1261,7 +1264,7 @@ moisture sensor polarity: ${ ) }`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -1270,7 +1273,7 @@ moisture sensor polarity: ${ // and status // Query the current system config - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying irrigation system status...", direction: "outbound", @@ -1278,15 +1281,15 @@ moisture sensor polarity: ${ await api.getSystemStatus(); // for each valve, query the current status and configuration - if (IrrigationCC.supportsMasterValveCached(applHost, endpoint)) { - applHost.controllerLog.logNode(node.id, { + if (IrrigationCC.supportsMasterValveCached(ctx, endpoint)) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying master valve configuration...", direction: "outbound", }); await api.getValveConfig("master"); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying master valve status...", direction: "outbound", @@ -1296,10 +1299,10 @@ moisture sensor polarity: ${ for ( let i = 1; - i <= (IrrigationCC.getNumValvesCached(applHost, endpoint) ?? 0); + i <= (IrrigationCC.getNumValvesCached(ctx, endpoint) ?? 0); i++ ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying configuration for valve ${ padStart( @@ -1312,7 +1315,7 @@ moisture sensor polarity: ${ }); await api.getValveConfig(i); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying status for valve ${ padStart( @@ -1328,7 +1331,7 @@ moisture sensor polarity: ${ } public translateProperty( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey?: string | number, ): string { @@ -1337,17 +1340,16 @@ moisture sensor polarity: ${ } else if (typeof property === "number") { return `Valve ${padStart(property.toString(), 3, "0")}`; } - return super.translateProperty(applHost, property, propertyKey); + return super.translateProperty(ctx, property, propertyKey); } } @CCCommand(IrrigationCommand.SystemInfoReport) export class IrrigationCCSystemInfoReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); this.supportsMasterValve = !!(this.payload[0] & 0x01); this.numValves = this.payload[1]; @@ -1367,9 +1369,9 @@ export class IrrigationCCSystemInfoReport extends IrrigationCC { @ccValue(IrrigationCCValues.maxValveTableSize) public readonly maxValveTableSize: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supports master valve": this.supportsMasterValve, "no. of valves": this.numValves, @@ -1387,10 +1389,9 @@ export class IrrigationCCSystemInfoGet extends IrrigationCC {} @CCCommand(IrrigationCommand.SystemStatusReport) export class IrrigationCCSystemStatusReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.systemVoltage = this.payload[0]; this.flowSensorActive = !!(this.payload[1] & 0x01); @@ -1474,7 +1475,7 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { @ccValue(IrrigationCCValues.firstOpenZoneId) public firstOpenZoneId?: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "system voltage": `${this.systemVoltage} V`, "active sensors": [ @@ -1515,7 +1516,7 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1538,12 +1539,11 @@ export type IrrigationCCSystemConfigSetOptions = { @useSupervision() export class IrrigationCCSystemConfigSet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (IrrigationCCSystemConfigSetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1565,7 +1565,7 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { public rainSensorPolarity?: IrrigationSensorPolarity; public moistureSensorPolarity?: IrrigationSensorPolarity; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { let polarity = 0; if (this.rainSensorPolarity != undefined) polarity |= 0b1; if (this.moistureSensorPolarity != undefined) polarity |= 0b10; @@ -1582,10 +1582,10 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { encodeFloatWithScale(this.lowPressureThreshold, 0 /* kPa */), Buffer.from([polarity]), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "master valve delay": `${this.masterValveDelay} s`, "high pressure threshold": `${this.highPressureThreshold} kPa`, @@ -1604,7 +1604,7 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { ); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1613,10 +1613,9 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { @CCCommand(IrrigationCommand.SystemConfigReport) export class IrrigationCCSystemConfigReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.masterValveDelay = this.payload[0]; let offset = 1; @@ -1660,7 +1659,7 @@ export class IrrigationCCSystemConfigReport extends IrrigationCC { @ccValue(IrrigationCCValues.moistureSensorPolarity) public readonly moistureSensorPolarity?: IrrigationSensorPolarity; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "master valve delay": `${this.masterValveDelay} s`, "high pressure threshold": `${this.highPressureThreshold} kPa`, @@ -1679,7 +1678,7 @@ export class IrrigationCCSystemConfigReport extends IrrigationCC { ); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1692,10 +1691,9 @@ export class IrrigationCCSystemConfigGet extends IrrigationCC {} @CCCommand(IrrigationCommand.ValveInfoReport) export class IrrigationCCValveInfoReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); if ((this.payload[0] & 0b1) === ValveType.MasterValve) { this.valveId = "master"; @@ -1726,51 +1724,51 @@ export class IrrigationCCValveInfoReport extends IrrigationCC { public readonly errorHighFlow?: boolean; public readonly errorLowFlow?: boolean; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // connected const valveConnectedValue = IrrigationCCValues.valveConnected( this.valveId, ); - this.ensureMetadata(applHost, valveConnectedValue); - this.setValue(applHost, valveConnectedValue, this.connected); + this.ensureMetadata(ctx, valveConnectedValue); + this.setValue(ctx, valveConnectedValue, this.connected); // nominalCurrent const nominalCurrentValue = IrrigationCCValues.nominalCurrent( this.valveId, ); - this.ensureMetadata(applHost, nominalCurrentValue); - this.setValue(applHost, nominalCurrentValue, this.nominalCurrent); + this.ensureMetadata(ctx, nominalCurrentValue); + this.setValue(ctx, nominalCurrentValue, this.nominalCurrent); // errorShortCircuit const errorShortCircuitValue = IrrigationCCValues.errorShortCircuit( this.valveId, ); - this.ensureMetadata(applHost, errorShortCircuitValue); - this.setValue(applHost, errorShortCircuitValue, this.errorShortCircuit); + this.ensureMetadata(ctx, errorShortCircuitValue); + this.setValue(ctx, errorShortCircuitValue, this.errorShortCircuit); // errorHighCurrent const errorHighCurrentValue = IrrigationCCValues.errorHighCurrent( this.valveId, ); - this.ensureMetadata(applHost, errorHighCurrentValue); - this.setValue(applHost, errorHighCurrentValue, this.errorHighCurrent); + this.ensureMetadata(ctx, errorHighCurrentValue); + this.setValue(ctx, errorHighCurrentValue, this.errorHighCurrent); // errorLowCurrent const errorLowCurrentValue = IrrigationCCValues.errorLowCurrent( this.valveId, ); - this.ensureMetadata(applHost, errorLowCurrentValue); - this.setValue(applHost, errorLowCurrentValue, this.errorLowCurrent); + this.ensureMetadata(ctx, errorLowCurrentValue); + this.setValue(ctx, errorLowCurrentValue, this.errorLowCurrent); if (this.errorMaximumFlow != undefined) { const errorMaximumFlowValue = IrrigationCCValues.errorMaximumFlow( this.valveId, ); - this.ensureMetadata(applHost, errorMaximumFlowValue); + this.ensureMetadata(ctx, errorMaximumFlowValue); this.setValue( - applHost, + ctx, errorMaximumFlowValue, this.errorMaximumFlow, ); @@ -1780,22 +1778,22 @@ export class IrrigationCCValveInfoReport extends IrrigationCC { const errorHighFlowValue = IrrigationCCValues.errorHighFlow( this.valveId, ); - this.ensureMetadata(applHost, errorHighFlowValue); - this.setValue(applHost, errorHighFlowValue, this.errorHighFlow); + this.ensureMetadata(ctx, errorHighFlowValue); + this.setValue(ctx, errorHighFlowValue, this.errorHighFlow); } if (this.errorLowFlow != undefined) { const errorLowFlowValue = IrrigationCCValues.errorLowFlow( this.valveId, ); - this.ensureMetadata(applHost, errorLowFlowValue); - this.setValue(applHost, errorLowFlowValue, this.errorLowFlow); + this.ensureMetadata(ctx, errorLowFlowValue); + this.setValue(ctx, errorLowFlowValue, this.errorLowFlow); } return true; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "valve ID": this.valveId, connected: this.connected, @@ -1813,7 +1811,7 @@ export class IrrigationCCValveInfoReport extends IrrigationCC { message.errors = errors.map((e) => `\n· ${e}`).join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1842,12 +1840,11 @@ function testResponseForIrrigationCommandWithValveId( ) export class IrrigationCCValveInfoGet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveInfoGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1861,17 +1858,17 @@ export class IrrigationCCValveInfoGet extends IrrigationCC { public valveId: ValveId; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "valve ID": this.valveId, }, @@ -1895,12 +1892,11 @@ export type IrrigationCCValveConfigSetOptions = { @useSupervision() export class IrrigationCCValveConfigSet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (IrrigationCCValveConfigSetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1930,7 +1926,7 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { public useRainSensor: boolean; public useMoistureSensor: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ this.valveId === "master" ? 1 : 0, @@ -1946,12 +1942,12 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { | (this.useMoistureSensor ? 0b10 : 0), ]), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "valve ID": this.valveId, "nominal current high threshold": @@ -1971,10 +1967,9 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { @CCCommand(IrrigationCommand.ValveConfigReport) export class IrrigationCCValveConfigReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); if ((this.payload[0] & 0b1) === ValveType.MasterValve) { this.valveId = "master"; @@ -2014,15 +2009,15 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { this.useMoistureSensor = !!(this.payload[offset] & 0b10); } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // nominalCurrentHighThreshold const nominalCurrentHighThresholdValue = IrrigationCCValues .nominalCurrentHighThreshold(this.valveId); - this.ensureMetadata(applHost, nominalCurrentHighThresholdValue); + this.ensureMetadata(ctx, nominalCurrentHighThresholdValue); this.setValue( - applHost, + ctx, nominalCurrentHighThresholdValue, this.nominalCurrentHighThreshold, ); @@ -2030,45 +2025,45 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { // nominalCurrentLowThreshold const nominalCurrentLowThresholdValue = IrrigationCCValues .nominalCurrentLowThreshold(this.valveId); - this.ensureMetadata(applHost, nominalCurrentLowThresholdValue); + this.ensureMetadata(ctx, nominalCurrentLowThresholdValue); this.setValue( - applHost, + ctx, nominalCurrentLowThresholdValue, this.nominalCurrentLowThreshold, ); // maximumFlow const maximumFlowValue = IrrigationCCValues.maximumFlow(this.valveId); - this.ensureMetadata(applHost, maximumFlowValue); - this.setValue(applHost, maximumFlowValue, this.maximumFlow); + this.ensureMetadata(ctx, maximumFlowValue); + this.setValue(ctx, maximumFlowValue, this.maximumFlow); // highFlowThreshold const highFlowThresholdValue = IrrigationCCValues.highFlowThreshold( this.valveId, ); - this.ensureMetadata(applHost, highFlowThresholdValue); - this.setValue(applHost, highFlowThresholdValue, this.highFlowThreshold); + this.ensureMetadata(ctx, highFlowThresholdValue); + this.setValue(ctx, highFlowThresholdValue, this.highFlowThreshold); // lowFlowThreshold const lowFlowThresholdValue = IrrigationCCValues.lowFlowThreshold( this.valveId, ); - this.ensureMetadata(applHost, lowFlowThresholdValue); - this.setValue(applHost, lowFlowThresholdValue, this.lowFlowThreshold); + this.ensureMetadata(ctx, lowFlowThresholdValue); + this.setValue(ctx, lowFlowThresholdValue, this.lowFlowThreshold); // useRainSensor const useRainSensorValue = IrrigationCCValues.useRainSensor( this.valveId, ); - this.ensureMetadata(applHost, useRainSensorValue); - this.setValue(applHost, useRainSensorValue, this.useRainSensor); + this.ensureMetadata(ctx, useRainSensorValue); + this.setValue(ctx, useRainSensorValue, this.useRainSensor); // useMoistureSensor const useMoistureSensorValue = IrrigationCCValues.useMoistureSensor( this.valveId, ); - this.ensureMetadata(applHost, useMoistureSensorValue); - this.setValue(applHost, useMoistureSensorValue, this.useMoistureSensor); + this.ensureMetadata(ctx, useMoistureSensorValue); + this.setValue(ctx, useMoistureSensorValue, this.useMoistureSensor); return true; } @@ -2082,9 +2077,9 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { public useRainSensor: boolean; public useMoistureSensor: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "valve ID": this.valveId, "nominal current high threshold": @@ -2113,12 +2108,11 @@ export interface IrrigationCCValveConfigGetOptions extends CCCommandOptions { ) export class IrrigationCCValveConfigGet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveConfigGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2132,17 +2126,17 @@ export class IrrigationCCValveConfigGet extends IrrigationCC { public valveId: ValveId; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "valve ID": this.valveId, }, @@ -2160,12 +2154,11 @@ export interface IrrigationCCValveRunOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveRun extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveRunOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2181,7 +2174,7 @@ export class IrrigationCCValveRun extends IrrigationCC { public valveId: ValveId; public duration: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.valveId === "master" ? 1 : 0, this.valveId === "master" ? 1 : this.valveId || 1, @@ -2189,10 +2182,10 @@ export class IrrigationCCValveRun extends IrrigationCC { 0, ]); this.payload.writeUInt16BE(this.duration, 2); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "valve ID": this.valveId, }; @@ -2202,7 +2195,7 @@ export class IrrigationCCValveRun extends IrrigationCC { message.action = "turn off"; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2218,12 +2211,11 @@ export interface IrrigationCCValveTableSetOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveTableSet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveTableSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2239,7 +2231,7 @@ export class IrrigationCCValveTableSet extends IrrigationCC { public tableId: number; public entries: ValveTableEntry[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(1 + this.entries.length * 3); this.payload[0] = this.tableId; for (let i = 0; i < this.entries.length; i++) { @@ -2248,10 +2240,10 @@ export class IrrigationCCValveTableSet extends IrrigationCC { this.payload[offset] = entry.valveId; this.payload.writeUInt16BE(entry.duration, offset + 1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "table ID": this.tableId, }; @@ -2265,7 +2257,7 @@ export class IrrigationCCValveTableSet extends IrrigationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2274,10 +2266,9 @@ export class IrrigationCCValveTableSet extends IrrigationCC { @CCCommand(IrrigationCommand.ValveTableReport) export class IrrigationCCValveTableReport extends IrrigationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload((this.payload.length - 1) % 3 === 0); this.tableId = this.payload[0]; this.entries = []; @@ -2292,7 +2283,7 @@ export class IrrigationCCValveTableReport extends IrrigationCC { public readonly tableId: number; public readonly entries: ValveTableEntry[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "table ID": this.tableId, }; @@ -2306,7 +2297,7 @@ export class IrrigationCCValveTableReport extends IrrigationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2331,12 +2322,11 @@ function testResponseForIrrigationValveTableGet( ) export class IrrigationCCValveTableGet extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveTableGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2350,14 +2340,14 @@ export class IrrigationCCValveTableGet extends IrrigationCC { public tableId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.tableId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "table ID": this.tableId, }, @@ -2374,12 +2364,11 @@ export interface IrrigationCCValveTableRunOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveTableRun extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCValveTableRunOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2399,14 +2388,14 @@ export class IrrigationCCValveTableRun extends IrrigationCC { public tableIDs: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from(this.tableIDs); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "table IDs": this.tableIDs .map((id) => padStart(id.toString(), 3, "0")) @@ -2429,12 +2418,11 @@ export interface IrrigationCCSystemShutoffOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCSystemShutoff extends IrrigationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | IrrigationCCSystemShutoffOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2448,14 +2436,14 @@ export class IrrigationCCSystemShutoff extends IrrigationCC { public duration?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.duration ?? 255]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { duration: this.duration === 0 ? "temporarily" diff --git a/packages/cc/src/cc/LanguageCC.ts b/packages/cc/src/cc/LanguageCC.ts index 02ce9f2c0dee..907727807b21 100644 --- a/packages/cc/src/cc/LanguageCC.ts +++ b/packages/cc/src/cc/LanguageCC.ts @@ -12,11 +12,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; @@ -24,6 +20,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -77,11 +75,11 @@ export class LanguageCCAPI extends CCAPI { public async get() { this.assertSupportsCommand(LanguageCommand, LanguageCommand.Get); - const cc = new LanguageCCGet(this.applHost, { + const cc = new LanguageCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -97,13 +95,13 @@ export class LanguageCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(LanguageCommand, LanguageCommand.Set); - const cc = new LanguageCCSet(this.applHost, { + const cc = new LanguageCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, language, country, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -113,33 +111,37 @@ export class LanguageCCAPI extends CCAPI { export class LanguageCC extends CommandClass { declare ccCommand: LanguageCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Language, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "requesting language setting...", direction: "outbound", }); @@ -149,7 +151,7 @@ export class LanguageCC extends CommandClass { const logMessage = `received current language setting: ${language}${ country != undefined ? `-${country}` : "" }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -167,10 +169,9 @@ export interface LanguageCCSetOptions extends CCCommandOptions { @useSupervision() export class LanguageCCSet extends LanguageCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | LanguageCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -215,20 +216,20 @@ export class LanguageCCSet extends LanguageCC { this._country = value; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(!!this._country ? 5 : 3); this.payload.write(this._language, 0, "ascii"); if (!!this._country) this.payload.write(this._country, 3, "ascii"); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { language: this.language }; if (this._country != undefined) { message.country = this._country; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -237,10 +238,9 @@ export class LanguageCCSet extends LanguageCC { @CCCommand(LanguageCommand.Report) export class LanguageCCReport extends LanguageCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); // if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.language = this.payload.toString("ascii", 0, 3); @@ -256,13 +256,13 @@ export class LanguageCCReport extends LanguageCC { @ccValue(LanguageCCValues.country) public readonly country: MaybeNotKnown; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { language: this.language }; if (this.country != undefined) { message.country = this.country; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/LockCC.ts b/packages/cc/src/cc/LockCC.ts index c7cf5b0095f3..41a1cc32f734 100644 --- a/packages/cc/src/cc/LockCC.ts +++ b/packages/cc/src/cc/LockCC.ts @@ -10,11 +10,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -30,6 +26,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -72,11 +70,11 @@ export class LockCCAPI extends PhysicalCCAPI { public async get(): Promise> { this.assertSupportsCommand(LockCommand, LockCommand.Get); - const cc = new LockCCGet(this.applHost, { + const cc = new LockCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -91,12 +89,12 @@ export class LockCCAPI extends PhysicalCCAPI { public async set(locked: boolean): Promise { this.assertSupportsCommand(LockCommand, LockCommand.Set); - const cc = new LockCCSet(this.applHost, { + const cc = new LockCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, locked, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -137,39 +135,43 @@ export class LockCCAPI extends PhysicalCCAPI { export class LockCC extends CommandClass { declare ccCommand: LockCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Lock, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "requesting current lock state...", direction: "outbound", }); const locked = await api.get(); const logMessage = `the lock is ${locked ? "locked" : "unlocked"}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -185,10 +187,9 @@ export interface LockCCSetOptions extends CCCommandOptions { @useSupervision() export class LockCCSet extends LockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | LockCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -202,14 +203,14 @@ export class LockCCSet extends LockCC { public locked: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.locked ? 1 : 0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { locked: this.locked }, }; } @@ -218,10 +219,9 @@ export class LockCCSet extends LockCC { @CCCommand(LockCommand.Report) export class LockCCReport extends LockCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.locked = this.payload[0] === 1; } @@ -229,9 +229,9 @@ export class LockCCReport extends LockCC { @ccValue(LockCCValues.locked) public readonly locked: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { locked: this.locked }, }; } diff --git a/packages/cc/src/cc/ManufacturerProprietaryCC.ts b/packages/cc/src/cc/ManufacturerProprietaryCC.ts index 8b71365c3f11..e16969216443 100644 --- a/packages/cc/src/cc/ManufacturerProprietaryCC.ts +++ b/packages/cc/src/cc/ManufacturerProprietaryCC.ts @@ -1,19 +1,19 @@ import { CommandClasses, - type IVirtualEndpoint, - type IZWaveEndpoint, ZWaveError, ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveApplicationHost, ZWaveHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext } from "@zwave-js/host/safe"; import { staticExtends } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; -import { CCAPI } from "../lib/API"; +import { CCAPI, type CCAPIEndpoint, type CCAPIHost } from "../lib/API"; import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -34,16 +34,16 @@ export type ManufacturerProprietaryCCConstructor< typeof ManufacturerProprietaryCC, > = T & { // I don't like the any, but we need it to support half-implemented CCs (e.g. report classes) - new (host: ZWaveHost, options: any): InstanceType; + new (options: any): InstanceType; }; @API(CommandClasses["Manufacturer Proprietary"]) export class ManufacturerProprietaryCCAPI extends CCAPI { public constructor( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + host: CCAPIHost, + endpoint: CCAPIEndpoint, ) { - super(applHost, endpoint); + super(host, endpoint); // Read the manufacturer ID from Manufacturer Specific CC const manufacturerId = this.getValueDB().getValue( @@ -58,7 +58,7 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { SpecificAPIConstructor != undefined && new.target !== SpecificAPIConstructor ) { - return new SpecificAPIConstructor(applHost, endpoint); + return new SpecificAPIConstructor(host, endpoint); } } } @@ -68,20 +68,20 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { manufacturerId: number, data?: Buffer, ): Promise { - const cc = new ManufacturerProprietaryCC(this.applHost, { + const cc = new ManufacturerProprietaryCC({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, manufacturerId, }); cc.payload = data ?? Buffer.allocUnsafe(0); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public async sendAndReceiveData(manufacturerId: number, data?: Buffer) { - const cc = new ManufacturerProprietaryCC(this.applHost, { + const cc = new ManufacturerProprietaryCC({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, manufacturerId, @@ -89,7 +89,7 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { }); cc.payload = data ?? Buffer.allocUnsafe(0); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerProprietaryCC >( cc, @@ -134,12 +134,11 @@ export class ManufacturerProprietaryCC extends CommandClass { declare ccCommand: undefined; public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ManufacturerProprietaryCCOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -156,7 +155,7 @@ export class ManufacturerProprietaryCC extends CommandClass { && new.target !== PCConstructor && !staticExtends(new.target, PCConstructor) ) { - return new PCConstructor(host, options); + return new PCConstructor(options); } // If the constructor is correct, update the payload for subclass deserialization @@ -192,7 +191,7 @@ export class ManufacturerProprietaryCC extends CommandClass { return this.manufacturerId; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const manufacturerId = this.getManufacturerIdOrThrow(); // ManufacturerProprietaryCC has no CC command, so the first byte // is stored in ccCommand @@ -205,7 +204,7 @@ export class ManufacturerProprietaryCC extends CommandClass { ]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public createSpecificInstance(): ManufacturerProprietaryCC | undefined { @@ -215,7 +214,7 @@ export class ManufacturerProprietaryCC extends CommandClass { this.manufacturerId, ); if (PCConstructor) { - return new PCConstructor(this.host, { + return new PCConstructor({ nodeId: this.nodeId, endpoint: this.endpointIndex, }); @@ -223,19 +222,21 @@ export class ManufacturerProprietaryCC extends CommandClass { } } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; // Read the manufacturer ID from Manufacturer Specific CC this.manufacturerId = this.getValue( - applHost, + ctx, ManufacturerSpecificCCValues.manufacturerId, )!; const pcInstance = this.createSpecificInstance(); if (pcInstance) { - await pcInstance.interview(applHost); + await pcInstance.interview(ctx); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `${this.constructor.name}: skipping interview refresh because the matching proprietary CC is not implemented...`, direction: "none", @@ -243,24 +244,26 @@ export class ManufacturerProprietaryCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; if (this.manufacturerId == undefined) { // Read the manufacturer ID from Manufacturer Specific CC this.manufacturerId = this.getValue( - applHost, + ctx, ManufacturerSpecificCCValues.manufacturerId, )!; } const pcInstance = this.createSpecificInstance(); if (pcInstance) { - await pcInstance.refreshValues(applHost); + await pcInstance.refreshValues(ctx); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `${this.constructor.name}: skipping value refresh because the matching proprietary CC is not implemented...`, direction: "none", diff --git a/packages/cc/src/cc/ManufacturerSpecificCC.ts b/packages/cc/src/cc/ManufacturerSpecificCC.ts index 0abea0a049c4..9f993fab4418 100644 --- a/packages/cc/src/cc/ManufacturerSpecificCC.ts +++ b/packages/cc/src/cc/ManufacturerSpecificCC.ts @@ -8,11 +8,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -20,6 +16,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -107,11 +104,11 @@ export class ManufacturerSpecificCCAPI extends PhysicalCCAPI { ManufacturerSpecificCommand.Get, ); - const cc = new ManufacturerSpecificCCGet(this.applHost, { + const cc = new ManufacturerSpecificCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerSpecificCCReport >( cc, @@ -135,12 +132,12 @@ export class ManufacturerSpecificCCAPI extends PhysicalCCAPI { ManufacturerSpecificCommand.DeviceSpecificGet, ); - const cc = new ManufacturerSpecificCCDeviceSpecificGet(this.applHost, { + const cc = new ManufacturerSpecificCCDeviceSpecificGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, deviceIdType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerSpecificCCDeviceSpecificReport >( cc, @@ -158,12 +155,12 @@ export class ManufacturerSpecificCCAPI extends PhysicalCCAPI { ManufacturerSpecificCommand.Report, ); - const cc = new ManufacturerSpecificCCReport(this.applHost, { + const cc = new ManufacturerSpecificCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -178,23 +175,25 @@ export class ManufacturerSpecificCC extends CommandClass { return []; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Manufacturer Specific"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery }); - if (!applHost.isControllerNode(node.id)) { - applHost.controllerLog.logNode(node.id, { + if (node.id !== ctx.ownNodeId) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying manufacturer information...", direction: "outbound", @@ -204,14 +203,14 @@ export class ManufacturerSpecificCC extends CommandClass { const logMessage = `received response for manufacturer information: manufacturer: ${ - applHost.configManager.lookupManufacturer( + ctx.lookupManufacturer( mfResp.manufacturerId, ) || "unknown" } (${num2hex(mfResp.manufacturerId)}) product type: ${num2hex(mfResp.productType)} product id: ${num2hex(mfResp.productId)}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -220,7 +219,7 @@ export class ManufacturerSpecificCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -234,12 +233,11 @@ export interface ManufacturerSpecificCCReportOptions { @CCCommand(ManufacturerSpecificCommand.Report) export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, options: | (ManufacturerSpecificCCReportOptions & CCCommandOptions) | CommandClassDeserializationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 6); @@ -262,17 +260,17 @@ export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { @ccValue(ManufacturerSpecificCCValues.productId) public readonly productId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(6); this.payload.writeUInt16BE(this.manufacturerId, 0); this.payload.writeUInt16BE(this.productType, 2); this.payload.writeUInt16BE(this.productId, 4); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "manufacturer id": num2hex(this.manufacturerId), "product type": num2hex(this.productType), @@ -291,10 +289,9 @@ export class ManufacturerSpecificCCDeviceSpecificReport extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.type = this.payload[0] & 0b111; @@ -317,9 +314,9 @@ export class ManufacturerSpecificCCDeviceSpecificReport ) public readonly deviceId: string; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "device id type": getEnumMemberName(DeviceIdType, this.type), "device id": this.deviceId, @@ -341,12 +338,11 @@ export class ManufacturerSpecificCCDeviceSpecificGet extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ManufacturerSpecificCCDeviceSpecificGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -359,14 +355,14 @@ export class ManufacturerSpecificCCDeviceSpecificGet public deviceIdType: DeviceIdType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([(this.deviceIdType || 0) & 0b111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "device id type": getEnumMemberName( DeviceIdType, diff --git a/packages/cc/src/cc/MeterCC.ts b/packages/cc/src/cc/MeterCC.ts index 3c0519351bb4..fc0cfd497f11 100644 --- a/packages/cc/src/cc/MeterCC.ts +++ b/packages/cc/src/cc/MeterCC.ts @@ -1,6 +1,5 @@ import { type FloatParameters, - type IZWaveEndpoint, type MaybeUnknown, encodeBitMask, encodeFloatWithScale, @@ -13,12 +12,17 @@ import { } from "@zwave-js/core"; import { CommandClasses, + type ControlsCC, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, type SinglecastCC, type SupervisionResult, + type SupportsCC, UNKNOWN_STATE, ValueMetadata, parseBitMask, @@ -26,9 +30,11 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { type AllOrNone, @@ -55,6 +61,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -269,23 +279,29 @@ export function isAccumulatedValue( switch (meterType) { case 0x01: // Electric return ( + // kVarh + // Pulse count scale === 0x00 // kWh || scale === 0x01 // kVAh - || scale === 0x03 // Pulse count - || scale === 0x08 // kVarh + || scale === 0x03 + || scale === 0x08 ); case 0x02: // Gas return ( + // Pulse count + // ft³ scale === 0x00 // m³ - || scale === 0x01 // ft³ - || scale === 0x03 // Pulse count + || scale === 0x01 + || scale === 0x03 ); case 0x03: // Water return ( + // Pulse count + // US gallons scale === 0x00 // m³ || scale === 0x01 // ft³ - || scale === 0x02 // US gallons - || scale === 0x03 // Pulse count + || scale === 0x02 + || scale === 0x03 ); case 0x04: // Heating return scale === 0x00; // kWh @@ -353,12 +369,12 @@ export class MeterCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(MeterCommand, MeterCommand.Get); - const cc = new MeterCCGet(this.applHost, { + const cc = new MeterCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -383,12 +399,12 @@ export class MeterCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(MeterCommand, MeterCommand.Report); - const cc = new MeterCCReport(this.applHost, { + const cc = new MeterCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -444,11 +460,11 @@ export class MeterCCAPI extends PhysicalCCAPI { public async getSupported() { this.assertSupportsCommand(MeterCommand, MeterCommand.SupportedGet); - const cc = new MeterCCSupportedGet(this.applHost, { + const cc = new MeterCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MeterCCSupportedReport >( cc, @@ -470,12 +486,12 @@ export class MeterCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(MeterCommand, MeterCommand.SupportedReport); - const cc = new MeterCCSupportedReport(this.applHost, { + const cc = new MeterCCSupportedReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -484,12 +500,12 @@ export class MeterCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(MeterCommand, MeterCommand.Reset); - const cc = new MeterCCReset(this.applHost, { + const cc = new MeterCCReset({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -628,25 +644,27 @@ export class MeterCCAPI extends PhysicalCCAPI { export class MeterCC extends CommandClass { declare ccCommand: MeterCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Meter, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying meter support...", direction: "outbound", @@ -673,13 +691,13 @@ supported rate types: ${ .join("") } supports reset: ${suppResp.supportsReset}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying meter support timed out, skipping interview...", @@ -690,46 +708,48 @@ supports reset: ${suppResp.supportsReset}`; } // Query current meter values - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Meter, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - if (this.version === 1) { - applHost.controllerLog.logNode(node.id, { + if (api.version === 1) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying default meter value...`, direction: "outbound", }); await api.get(); } else { - const type: number = this.getValue(applHost, MeterCCValues.type) + const type: number = this.getValue(ctx, MeterCCValues.type) ?? 0; const supportedScales: readonly number[] = - this.getValue(applHost, MeterCCValues.supportedScales) ?? []; + this.getValue(ctx, MeterCCValues.supportedScales) ?? []; const supportedRateTypes: readonly RateType[] = - this.getValue(applHost, MeterCCValues.supportedRateTypes) ?? []; + this.getValue(ctx, MeterCCValues.supportedRateTypes) ?? []; const rateTypes = supportedRateTypes.length ? supportedRateTypes : [undefined]; for (const rateType of rateTypes) { for (const scale of supportedScales) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying meter value (type = ${ getMeterName(type) @@ -756,15 +776,21 @@ supports reset: ${suppResp.supportsReset}`; public shouldRefreshValues( this: SinglecastCC, - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): boolean { // Poll the device when all of the supported values were last updated longer than 6 hours ago. // This may lead to some values not being updated, but the user may have disabled some unnecessary // reports to reduce traffic. - const valueDB = applHost.tryGetValueDB(this.nodeId); + const valueDB = ctx.tryGetValueDB(this.nodeId); if (!valueDB) return true; - const values = this.getDefinedValueIDs(applHost).filter((v) => + const values = this.getDefinedValueIDs(ctx).filter((v) => MeterCCValues.value.is(v) ); return values.every((v) => { @@ -781,10 +807,10 @@ supports reset: ${suppResp.supportsReset}`; * This only works AFTER the interview process */ public static getMeterTypeCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue(MeterCCValues.type.endpoint(endpoint.index)); } @@ -794,10 +820,10 @@ supports reset: ${suppResp.supportsReset}`; * This only works AFTER the interview process */ public static getSupportedScalesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue(MeterCCValues.supportedScales.endpoint(endpoint.index)); } @@ -807,10 +833,10 @@ supports reset: ${suppResp.supportsReset}`; * This only works AFTER the interview process */ public static supportsResetCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue(MeterCCValues.supportsReset.endpoint(endpoint.index)); } @@ -820,10 +846,10 @@ supports reset: ${suppResp.supportsReset}`; * This only works AFTER the interview process */ public static getSupportedRateTypesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( MeterCCValues.supportedRateTypes.endpoint(endpoint.index), @@ -831,7 +857,7 @@ supports reset: ${suppResp.supportsReset}`; } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -855,7 +881,7 @@ supports reset: ${suppResp.supportsReset}`; } else if (property === "reset" && typeof propertyKey === "number") { return getMeterName(propertyKey); } - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } @@ -872,12 +898,11 @@ export interface MeterCCReportOptions { @CCCommand(MeterCommand.Report) export class MeterCCReport extends MeterCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MeterCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const { type, rateType, scale1, value, bytesRead } = @@ -925,15 +950,17 @@ export class MeterCCReport extends MeterCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + + const ccVersion = getEffectiveCCVersion(ctx, this); const meter = getMeter(this.type); const scale = getMeterScale(this.type, this.scale) ?? getUnknownMeterScale(this.scale); // Filter out unknown meter types and scales, unless the strict validation is disabled - const measurementValidation = !this.host.getDeviceConfig?.( + const measurementValidation = !ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.disableStrictMeasurementValidation; @@ -946,9 +973,9 @@ export class MeterCCReport extends MeterCC { )(scale.label !== getUnknownMeterScale(this.scale).label); // Filter out unsupported meter types, scales and rate types if possible - if (this.version >= 2) { + if (ccVersion >= 2) { const expectedType = this.getValue( - applHost, + ctx, MeterCCValues.type, ); if (expectedType != undefined) { @@ -958,7 +985,7 @@ export class MeterCCReport extends MeterCC { } const supportedScales = this.getValue( - applHost, + ctx, MeterCCValues.supportedScales, ); if (supportedScales?.length) { @@ -968,7 +995,7 @@ export class MeterCCReport extends MeterCC { } const supportedRateTypes = this.getValue( - applHost, + ctx, MeterCCValues.supportedRateTypes, ); if (supportedRateTypes?.length) { @@ -989,7 +1016,7 @@ export class MeterCCReport extends MeterCC { this.rateType, this.scale, ); - this.setMetadata(applHost, valueValue, { + this.setMetadata(ctx, valueValue, { ...valueValue.meta, label: getValueLabel(this.type, this.scale, this.rateType), unit: scale.label, @@ -999,7 +1026,7 @@ export class MeterCCReport extends MeterCC { rateType: this.rateType, }, }); - this.setValue(applHost, valueValue, this.value); + this.setValue(ctx, valueValue, this.value); return true; } @@ -1011,7 +1038,7 @@ export class MeterCCReport extends MeterCC { public rateType: RateType; public deltaTime: MaybeUnknown; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const { data: typeAndValue, floatParams, scale2 } = encodeMeterValueAndInfo( this.type, @@ -1050,10 +1077,10 @@ export class MeterCCReport extends MeterCC { ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const scale = getMeterScale(this.type, this.scale) ?? getUnknownMeterScale(this.scale); @@ -1070,7 +1097,7 @@ export class MeterCCReport extends MeterCC { message["prev. value"] = this.previousValue; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1095,12 +1122,11 @@ export interface MeterCCGetOptions { @expectedCCResponse(MeterCCReport, testResponseForMeterGet) export class MeterCCGet extends MeterCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MeterCCGetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length >= 1) { this.rateType = (this.payload[0] & 0b11_000_000) >>> 6; @@ -1119,21 +1145,22 @@ export class MeterCCGet extends MeterCC { public rateType: RateType | undefined; public scale: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { let scale1: number; let scale2: number | undefined; let bufferLength = 0; + const ccVersion = getEffectiveCCVersion(ctx, this); if (this.scale == undefined) { scale1 = 0; - } else if (this.version >= 4 && this.scale >= 7) { + } else if (ccVersion >= 4 && this.scale >= 7) { scale1 = 7; scale2 = this.scale >>> 3; bufferLength = 2; - } else if (this.version >= 3) { + } else if (ccVersion >= 3) { scale1 = this.scale & 0b111; bufferLength = 1; - } else if (this.version >= 2) { + } else if (ccVersion >= 2) { scale1 = this.scale & 0b11; bufferLength = 1; } else { @@ -1141,7 +1168,7 @@ export class MeterCCGet extends MeterCC { } let rateTypeFlags = 0; - if (this.version >= 4 && this.rateType != undefined) { + if (ccVersion >= 4 && this.rateType != undefined) { rateTypeFlags = this.rateType & 0b11; bufferLength = Math.max(bufferLength, 1); } @@ -1150,18 +1177,18 @@ export class MeterCCGet extends MeterCC { this.payload[0] = (rateTypeFlags << 6) | (scale1 << 3); if (scale2) this.payload[1] = scale2; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.rateType != undefined) { message["rate type"] = getEnumMemberName(RateType, this.rateType); } if (this.scale != undefined) { - if (host) { + if (ctx) { // Try to lookup the meter type to translate the scale - const type = this.getValue(host, MeterCCValues.type); + const type = this.getValue(ctx, MeterCCValues.type); if (type != undefined) { message.scale = (getMeterScale(type, this.scale) ?? getUnknownMeterScale(this.scale)).label; @@ -1171,7 +1198,7 @@ export class MeterCCGet extends MeterCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1188,12 +1215,11 @@ export interface MeterCCSupportedReportOptions { @CCCommand(MeterCommand.SupportedReport) export class MeterCCSupportedReport extends MeterCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MeterCCSupportedReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -1246,13 +1272,15 @@ export class MeterCCSupportedReport extends MeterCC { @ccValue(MeterCCValues.supportedRateTypes) public readonly supportedRateTypes: readonly RateType[]; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; if (!this.supportsReset) return true; + const ccVersion = getEffectiveCCVersion(ctx, this); + // Create reset values - if (this.version < 6) { - this.ensureMetadata(applHost, MeterCCValues.resetAll); + if (ccVersion < 6) { + this.ensureMetadata(ctx, MeterCCValues.resetAll); } else { for (const scale of this.supportedScales) { // Only accumulated values can be reset @@ -1264,7 +1292,7 @@ export class MeterCCSupportedReport extends MeterCC { rateType, scale, ); - this.ensureMetadata(applHost, resetSingleValue, { + this.ensureMetadata(ctx, resetSingleValue, { ...resetSingleValue.meta, label: `Reset ${ getValueLabel( @@ -1280,7 +1308,7 @@ export class MeterCCSupportedReport extends MeterCC { return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const typeByte = (this.type & 0b0_00_11111) | (this.supportedRateTypes.includes(RateType.Consumed) ? 0b0_01_00000 @@ -1312,10 +1340,10 @@ export class MeterCCSupportedReport extends MeterCC { ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "meter type": getMeterName(this.type), "supports reset": this.supportsReset, @@ -1332,7 +1360,7 @@ export class MeterCCSupportedReport extends MeterCC { .join(", "), }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1354,12 +1382,11 @@ export type MeterCCResetOptions = AllOrNone<{ @useSupervision() export class MeterCCReset extends MeterCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MeterCCResetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length > 0) { const { @@ -1387,7 +1414,7 @@ export class MeterCCReset extends MeterCC { public rateType: RateType | undefined; public targetValue: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if ( this.type != undefined && this.scale != undefined @@ -1410,10 +1437,10 @@ export class MeterCCReset extends MeterCC { ]); } } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.type != undefined) { message.type = getMeterName(this.type); @@ -1429,7 +1456,7 @@ export class MeterCCReset extends MeterCC { message["target value"] = this.targetValue; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/MultiChannelAssociationCC.ts b/packages/cc/src/cc/MultiChannelAssociationCC.ts index 4909baab2baa..15dd6dd36a81 100644 --- a/packages/cc/src/cc/MultiChannelAssociationCC.ts +++ b/packages/cc/src/cc/MultiChannelAssociationCC.ts @@ -1,5 +1,5 @@ import type { - IZWaveEndpoint, + EndpointId, MessageRecord, SupervisionResult, } from "@zwave-js/core/safe"; @@ -16,9 +16,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -27,6 +27,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -202,14 +204,11 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { MultiChannelAssociationCommand.SupportedGroupingsGet, ); - const cc = new MultiChannelAssociationCCSupportedGroupingsGet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - }, - ); - const response = await this.applHost.sendCommand< + const cc = new MultiChannelAssociationCCSupportedGroupingsGet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + }); + const response = await this.host.sendCommand< MultiChannelAssociationCCSupportedGroupingsReport >( cc, @@ -225,15 +224,12 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { MultiChannelAssociationCommand.SupportedGroupingsReport, ); - const cc = new MultiChannelAssociationCCSupportedGroupingsReport( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - groupCount, - }, - ); - await this.applHost.sendCommand(cc, this.commandOptions); + const cc = new MultiChannelAssociationCCSupportedGroupingsReport({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + groupCount, + }); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -247,12 +243,12 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { MultiChannelAssociationCommand.Get, ); - const cc = new MultiChannelAssociationCCGet(this.applHost, { + const cc = new MultiChannelAssociationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelAssociationCCReport >( cc, @@ -272,12 +268,12 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { MultiChannelAssociationCommand.Report, ); - const cc = new MultiChannelAssociationCCReport(this.applHost, { + const cc = new MultiChannelAssociationCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -292,12 +288,12 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { MultiChannelAssociationCommand.Set, ); - const cc = new MultiChannelAssociationCCSet(this.applHost, { + const cc = new MultiChannelAssociationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -317,11 +313,11 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { // We don't want to do too much work, so find out which groups the destination is in const currentDestinations = MultiChannelAssociationCC .getAllDestinationsCached( - this.applHost, + this.host, this.endpoint, ); for (const [group, destinations] of currentDestinations) { - const cc = new MultiChannelAssociationCCRemove(this.applHost, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId: group, @@ -334,15 +330,20 @@ export class MultiChannelAssociationCCAPI extends PhysicalCCAPI { ), }); // TODO: evaluate intermediate supervision results - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } + } else if (options.groupId && options.groupId < 0) { + throw new ZWaveError( + "The group id must not be negative!", + ZWaveErrorCodes.Argument_Invalid, + ); } else { - const cc = new MultiChannelAssociationCCRemove(this.applHost, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } } @@ -370,18 +371,16 @@ export class MultiChannelAssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getGroupCountCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - MultiChannelAssociationCCValues.groupCount.endpoint( - endpoint.index, - ), - ) || 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + MultiChannelAssociationCCValues.groupCount.endpoint( + endpoint.index, + ), + ) || 0; } /** @@ -389,19 +388,17 @@ export class MultiChannelAssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getMaxNodesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, groupId: number, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - MultiChannelAssociationCCValues.maxNodes(groupId).endpoint( - endpoint.index, - ), - ) ?? 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + MultiChannelAssociationCCValues.maxNodes(groupId).endpoint( + endpoint.index, + ), + ) ?? 0; } /** @@ -409,12 +406,12 @@ export class MultiChannelAssociationCC extends CommandClass { * This only works AFTER the interview process */ public static getAllDestinationsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): ReadonlyMap { const ret = new Map(); - const groupCount = this.getGroupCountCached(applHost, endpoint); - const valueDB = applHost.getValueDB(endpoint.nodeId); + const groupCount = this.getGroupCountCached(ctx, endpoint); + const valueDB = ctx.getValueDB(endpoint.nodeId); for (let i = 1; i <= groupCount; i++) { const groupDestinations: AssociationAddress[] = []; // Add all node destinations @@ -462,37 +459,39 @@ export class MultiChannelAssociationCC extends CommandClass { return ret; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const mcAPI = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, endpoint, ); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // First find out how many groups are supported as multi channel - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying number of multi channel association groups...", direction: "outbound", }); const mcGroupCount = await mcAPI.getGroupCount(); if (mcGroupCount != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `supports ${mcGroupCount} multi channel association groups`, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying multi channel association groups timed out, skipping interview...", @@ -502,46 +501,48 @@ export class MultiChannelAssociationCC extends CommandClass { } // Query each association group for its members - await this.refreshValues(applHost); + await this.refreshValues(ctx); // And set up lifeline associations - await ccUtils.configureLifelineAssociations(applHost, endpoint); + await ccUtils.configureLifelineAssociations(ctx, endpoint); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const mcAPI = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const assocAPI = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const mcGroupCount: number = this.getValue( - applHost, + ctx, MultiChannelAssociationCCValues.groupCount, ) ?? 0; // Some devices report more association groups than multi channel association groups, so we need this info here const assocGroupCount: number = - this.getValue(applHost, AssociationCCValues.groupCount) + this.getValue(ctx, AssociationCCValues.groupCount) || mcGroupCount; // Then query each multi channel association group for (let groupId = 1; groupId <= mcGroupCount; groupId++) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying multi channel association group #${groupId}...`, @@ -566,7 +567,7 @@ currently assigned endpoints: ${ }) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -575,7 +576,7 @@ currently assigned endpoints: ${ // Check if there are more non-multi-channel association groups we haven't queried yet if (assocAPI.isSupported() && assocGroupCount > mcGroupCount) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying additional non-multi-channel association groups...`, @@ -586,7 +587,7 @@ currently assigned endpoints: ${ groupId <= assocGroupCount; groupId++ ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying association group #${groupId}...`, direction: "outbound", @@ -597,7 +598,7 @@ currently assigned endpoints: ${ `received information for association group #${groupId}: maximum # of nodes: ${group.maxNodes} currently assigned nodes: ${group.nodeIds.map(String).join(", ")}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -622,12 +623,11 @@ export type MultiChannelAssociationCCSetOptions = @useSupervision() export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MultiChannelAssociationCCSetOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.groupId = this.payload[0]; @@ -659,7 +659,7 @@ export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { public nodeIds: number[]; public endpoints: EndpointAddress[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.groupId]), serializeMultiChannelAssociationDestination( @@ -667,12 +667,12 @@ export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { this.endpoints, ), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "node ids": this.nodeIds.join(", "), @@ -696,12 +696,11 @@ export interface MultiChannelAssociationCCRemoveOptions { @useSupervision() export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MultiChannelAssociationCCRemoveOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.groupId = this.payload[0]; @@ -710,22 +709,6 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { this.payload.subarray(1), )); } else { - // Validate options - if (!options.groupId) { - if (this.version === 1) { - throw new ZWaveError( - `Node ${this - .nodeId as number} only supports MultiChannelAssociationCC V1 which requires the group Id to be set`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - } else if (options.groupId < 0) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - // When removing associations, we allow invalid node IDs. // See GH#3606 - it is possible that those exist. this.groupId = options.groupId; @@ -738,7 +721,7 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { public nodeIds?: number[]; public endpoints?: EndpointAddress[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.groupId || 0]), serializeMultiChannelAssociationDestination( @@ -746,10 +729,10 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { this.endpoints || [], ), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "group id": this.groupId || "(all groups)", }; @@ -760,7 +743,7 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { message.endpoints = endpointAddressesToString(this.endpoints); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -778,12 +761,11 @@ export interface MultiChannelAssociationCCReportOptions { @CCCommand(MultiChannelAssociationCommand.Report) export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (MultiChannelAssociationCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -835,8 +817,8 @@ export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: MultiChannelAssociationCCReport[], + _ctx: CCParsingContext, ): void { // Concat the list of nodes this.nodeIds = [...partials, this] @@ -848,7 +830,7 @@ export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const destinations = serializeMultiChannelAssociationDestination( this.nodeIds, this.endpoints, @@ -861,12 +843,12 @@ export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { ]), destinations, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "maximum # of nodes": this.maxNodes, @@ -886,12 +868,11 @@ export interface MultiChannelAssociationCCGetOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelAssociationCCReport) export class MultiChannelAssociationCCGet extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelAssociationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.groupId = this.payload[0]; @@ -908,14 +889,14 @@ export class MultiChannelAssociationCCGet extends MultiChannelAssociationCC { public groupId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId }, }; } @@ -933,12 +914,11 @@ export class MultiChannelAssociationCCSupportedGroupingsReport extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelAssociationCCSupportedGroupingsReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -951,14 +931,14 @@ export class MultiChannelAssociationCCSupportedGroupingsReport @ccValue(MultiChannelAssociationCCValues.groupCount) public readonly groupCount: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupCount]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group count": this.groupCount }, }; } diff --git a/packages/cc/src/cc/MultiChannelCC.ts b/packages/cc/src/cc/MultiChannelCC.ts index 23a327e99cb0..af3069711f05 100644 --- a/packages/cc/src/cc/MultiChannelCC.ts +++ b/packages/cc/src/cc/MultiChannelCC.ts @@ -2,7 +2,6 @@ import { type ApplicationNodeInformation, CommandClasses, type GenericDeviceClass, - type IZWaveNode, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -20,9 +19,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { distinct } from "alcalzone-shared/arrays"; @@ -31,6 +30,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -129,8 +131,8 @@ export const MultiChannelCCValues = Object.freeze({ * This function gives an estimate if this is the case (i.e. all endpoints have a different device class) */ function areEndpointsUnnecessary( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, endpointIndizes: number[], ): boolean { // Gather all device classes @@ -144,7 +146,7 @@ function areEndpointsUnnecessary( for (const endpoint of endpointIndizes) { const devClassValueId = MultiChannelCCValues.endpointDeviceClass .endpoint(endpoint); - const deviceClass = applHost.getValueDB(node.id).getValue<{ + const deviceClass = ctx.getValueDB(nodeId).getValue<{ generic: number; specific: number; }>(devClassValueId); @@ -214,11 +216,11 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.EndPointGet, ); - const cc = new MultiChannelCCEndPointGet(this.applHost, { + const cc = new MultiChannelCCEndPointGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCEndPointReport >( cc, @@ -243,12 +245,12 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.CapabilityGet, ); - const cc = new MultiChannelCCCapabilityGet(this.applHost, { + const cc = new MultiChannelCCCapabilityGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, requestedEndpoint: endpoint, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCCapabilityReport >( cc, @@ -282,13 +284,13 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.EndPointFind, ); - const cc = new MultiChannelCCEndPointFind(this.applHost, { + const cc = new MultiChannelCCEndPointFind({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, genericClass, specificClass, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCEndPointFindReport >( cc, @@ -306,12 +308,12 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.AggregatedMembersGet, ); - const cc = new MultiChannelCCAggregatedMembersGet(this.applHost, { + const cc = new MultiChannelCCAggregatedMembersGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, requestedEndpoint: endpoint, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCAggregatedMembersReport >( cc, @@ -334,11 +336,11 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.CommandEncapsulation, ); - const cc = new MultiChannelCCCommandEncapsulation(this.applHost, { + const cc = new MultiChannelCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -350,11 +352,11 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.GetV1, ); - const cc = new MultiChannelCCV1Get(this.applHost, { + const cc = new MultiChannelCCV1Get({ nodeId: this.endpoint.nodeId, requestedCC: ccId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCV1Report >( cc, @@ -372,11 +374,11 @@ export class MultiChannelCCAPI extends CCAPI { MultiChannelCommand.CommandEncapsulationV1, ); - const cc = new MultiChannelCCV1CommandEncapsulation(this.applHost, { + const cc = new MultiChannelCCV1CommandEncapsulation({ nodeId: this.endpoint.nodeId, encapsulated, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -403,33 +405,30 @@ export class MultiChannelCC extends CommandClass { ); } - /** Encapsulates a command that targets a specific endpoint */ + /** Encapsulates a command that targets a specific endpoint, with version 2+ of the Multi Channel CC */ public static encapsulate( - host: ZWaveHost, cc: CommandClass, - ): - | MultiChannelCCCommandEncapsulation - | MultiChannelCCV1CommandEncapsulation - { - const ccVersion = host.getSafeCCVersion( - CommandClasses["Multi Channel"], - cc.nodeId as number, - ); - let ret: - | MultiChannelCCCommandEncapsulation - | MultiChannelCCV1CommandEncapsulation; - if (ccVersion === 1) { - ret = new MultiChannelCCV1CommandEncapsulation(host, { - nodeId: cc.nodeId, - encapsulated: cc, - }); - } else { - ret = new MultiChannelCCCommandEncapsulation(host, { - nodeId: cc.nodeId, - encapsulated: cc, - destination: cc.endpointIndex, - }); - } + ): MultiChannelCCCommandEncapsulation { + const ret = new MultiChannelCCCommandEncapsulation({ + nodeId: cc.nodeId, + encapsulated: cc, + destination: cc.endpointIndex, + }); + + // Copy the encapsulation flags from the encapsulated command + ret.encapsulationFlags = cc.encapsulationFlags; + + return ret; + } + + /** Encapsulates a command that targets a specific endpoint, with version 1 of the Multi Channel CC */ + public static encapsulateV1( + cc: CommandClass, + ): MultiChannelCCV1CommandEncapsulation { + const ret = new MultiChannelCCV1CommandEncapsulation({ + nodeId: cc.nodeId, + encapsulated: cc, + }); // Copy the encapsulation flags from the encapsulated command ret.encapsulationFlags = cc.encapsulationFlags; @@ -442,13 +441,13 @@ export class MultiChannelCC extends CommandClass { return true; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview(ctx: InterviewContext): Promise { + const node = this.getNode(ctx)!; - const removeEndpoints = applHost.getDeviceConfig?.(node.id)?.compat + const removeEndpoints = ctx.getDeviceConfig?.(node.id)?.compat ?.removeEndpoints; if (removeEndpoints === "*") { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Skipping ${this.ccName} interview b/c all endpoints are ignored by the device config file...`, @@ -457,34 +456,35 @@ export class MultiChannelCC extends CommandClass { return; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Special interview procedure for legacy nodes - if (this.version === 1) return this.interviewV1(applHost); + const ccVersion = getEffectiveCCVersion(ctx, this); + if (ccVersion === 1) return this.interviewV1(ctx); const endpoint = node.getEndpoint(this.endpointIndex)!; const api = CCAPI.create( CommandClasses["Multi Channel"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); // Step 1: Retrieve general information about end points - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying device endpoint information...", direction: "outbound", }); const multiResponse = await api.getEndpoints(); if (!multiResponse) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying device endpoint information timed out, aborting interview...", @@ -501,7 +501,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; logMessage += `\nendpoint count (aggregated): ${multiResponse.aggregatedEndpointCount}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -521,7 +521,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; }; if (api.supportsCommand(MultiChannelCommand.EndPointFind)) { // Step 2a: Find all endpoints - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying all endpoints...", direction: "outbound", @@ -531,7 +531,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; if (foundEndpoints) allEndpoints.push(...foundEndpoints); if (!allEndpoints.length) { // Create a sequential list of endpoints - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Endpoint query returned no results, assuming that endpoints are sequential`, @@ -539,7 +539,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; }); addSequentialEndpoints(); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received endpoints: ${ allEndpoints @@ -551,7 +551,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; } } else { // Step 2b: Assume that the endpoints are in sequential order - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `does not support EndPointFind, assuming that endpoints are sequential`, @@ -562,7 +562,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; // Step 2.5: remove ignored endpoints if (removeEndpoints?.length) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `The following endpoints are ignored through the config file: ${ @@ -582,10 +582,10 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; for (const endpoint of allEndpoints) { if ( endpoint > multiResponse.individualEndpointCount - && this.version >= 4 + && ccVersion >= 4 ) { // Find members of aggregated end point - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying members of aggregated endpoint #${endpoint}...`, @@ -593,7 +593,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; }); const members = await api.getAggregatedMembers(endpoint); if (members) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `aggregated endpoint #${endpoint} has members ${ @@ -609,7 +609,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; // When the device reports identical capabilities for all endpoints, // we don't need to query them all if (multiResponse.identicalCapabilities && hasQueriedCapabilities) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `all endpoints identical, skipping capability query for endpoint #${endpoint}...`, @@ -637,7 +637,7 @@ identical capabilities: ${multiResponse.identicalCapabilities}`; continue; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying capabilities for endpoint #${endpoint}...`, direction: "outbound", @@ -654,13 +654,13 @@ supported CCs:`; for (const cc of caps.supportedCCs) { logMessage += `\n · ${getCCName(cc)}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying endpoint #${endpoint} capabilities timed out, aborting interview...`, @@ -674,19 +674,19 @@ supported CCs:`; // But first figure out if they seem unnecessary and if they do, which ones should be preserved if ( !multiResponse.identicalCapabilities - && areEndpointsUnnecessary(applHost, node, allEndpoints) + && areEndpointsUnnecessary(ctx, node.id, allEndpoints) ) { - const preserve = applHost.getDeviceConfig?.(node.id)?.compat + const preserve = ctx.getDeviceConfig?.(node.id)?.compat ?.preserveEndpoints; if (!preserve) { allEndpoints = []; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Endpoints seem unnecessary b/c they have different device classes, ignoring all...`, }); } else if (preserve === "*") { // preserve all endpoints, do nothing - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Endpoints seem unnecessary, but are configured to be preserved.`, }); @@ -694,7 +694,7 @@ supported CCs:`; allEndpoints = allEndpoints.filter((ep) => preserve.includes(ep) ); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Endpoints seem unnecessary, but endpoints ${ allEndpoints.join( ", ", @@ -704,24 +704,24 @@ supported CCs:`; } } this.setValue( - applHost, + ctx, MultiChannelCCValues.endpointIndizes, allEndpoints, ); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - private async interviewV1(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + private async interviewV1(ctx: InterviewContext): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Multi Channel"], - applHost, + ctx, endpoint, ); - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); // V1 works the opposite way - we scan all CCs and remember how many // endpoints they have @@ -733,14 +733,13 @@ supported CCs:`; .filter( (cc) => !CommandClass.createInstanceUnchecked( - applHost, node, cc, )?.skipEndpointInterview(), ); const endpointCounts = new Map(); for (const ccId of supportedCCs) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `Querying endpoint count for CommandClass ${ getCCName( ccId, @@ -752,7 +751,7 @@ supported CCs:`; if (endpointCount != undefined) { endpointCounts.set(ccId, endpointCount); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `CommandClass ${ getCCName( ccId, @@ -767,24 +766,24 @@ supported CCs:`; // We have only individual and no dynamic and no aggregated endpoints const numEndpoints = Math.max(...endpointCounts.values()); this.setValue( - applHost, + ctx, MultiChannelCCValues.endpointCountIsDynamic, false, ); this.setValue( - applHost, + ctx, MultiChannelCCValues.aggregatedEndpointCount, 0, ); this.setValue( - applHost, + ctx, MultiChannelCCValues.individualEndpointCount, numEndpoints, ); // Since we queried all CCs separately, we can assume that all // endpoints have different capabilities this.setValue( - applHost, + ctx, MultiChannelCCValues.endpointsHaveIdenticalCapabilities, false, ); @@ -802,7 +801,7 @@ supported CCs:`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -817,19 +816,18 @@ export interface MultiChannelCCEndPointReportOptions extends CCCommandOptions { @CCCommand(MultiChannelCommand.EndPointReport) export class MultiChannelCCEndPointReport extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCEndPointReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.countIsDynamic = !!(this.payload[0] & 0b10000000); this.identicalCapabilities = !!(this.payload[0] & 0b01000000); this.individualCount = this.payload[1] & 0b01111111; - if (this.version >= 4 && this.payload.length >= 3) { + if (this.payload.length >= 3) { this.aggregatedCount = this.payload[2] & 0b01111111; } } else { @@ -852,17 +850,17 @@ export class MultiChannelCCEndPointReport extends MultiChannelCC { @ccValue(MultiChannelCCValues.aggregatedEndpointCount) public aggregatedCount: MaybeNotKnown; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ (this.countIsDynamic ? 0b10000000 : 0) | (this.identicalCapabilities ? 0b01000000 : 0), this.individualCount & 0b01111111, this.aggregatedCount ?? 0, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "endpoint count (individual)": this.individualCount, "count is dynamic": this.countIsDynamic, @@ -872,7 +870,7 @@ export class MultiChannelCCEndPointReport extends MultiChannelCC { message["endpoint count (aggregated)"] = this.aggregatedCount; } const ret = { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; return ret; @@ -900,12 +898,11 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC implements ApplicationNodeInformation { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCCapabilityReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Only validate the bytes we expect to see here @@ -935,21 +932,21 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const deviceClassValue = MultiChannelCCValues.endpointDeviceClass; const ccsValue = MultiChannelCCValues.endpointCCs; if (this.wasRemoved) { - this.removeValue(applHost, deviceClassValue); - this.removeValue(applHost, ccsValue); + this.removeValue(ctx, deviceClassValue); + this.removeValue(ctx, ccsValue); } else { - this.setValue(applHost, deviceClassValue, { + this.setValue(ctx, deviceClassValue, { generic: this.genericDeviceClass, specific: this.specificDeviceClass, }); - this.setValue(applHost, ccsValue, this.supportedCCs); + this.setValue(ctx, ccsValue, this.supportedCCs); } return true; } @@ -961,7 +958,7 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC public readonly isDynamic: boolean; public readonly wasRemoved: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ (this.endpointIndex & 0b01111111) @@ -969,12 +966,12 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC ]), encodeApplicationNodeInformation(this), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "endpoint index": this.endpointIndex, "generic device class": getGenericDeviceClass( @@ -1012,12 +1009,11 @@ function testResponseForMultiChannelCapabilityGet( ) export class MultiChannelCCCapabilityGet extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCCapabilityGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.requestedEndpoint = this.payload[0] & 0b01111111; @@ -1028,14 +1024,14 @@ export class MultiChannelCCCapabilityGet extends MultiChannelCC { public requestedEndpoint: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.requestedEndpoint & 0b01111111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { endpoint: this.requestedEndpoint }, }; } @@ -1054,12 +1050,11 @@ export interface MultiChannelCCEndPointFindReportOptions @CCCommand(MultiChannelCommand.EndPointFindReport) export class MultiChannelCCEndPointFindReport extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCEndPointFindReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -1085,7 +1080,7 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { public foundEndpoints: number[]; public reportsToFollow: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ this.reportsToFollow, @@ -1094,7 +1089,7 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { ]), Buffer.from(this.foundEndpoints.map((e) => e & 0b01111111)), ]); - return super.serialize(); + return super.serialize(ctx); } public getPartialCCSessionId(): Record | undefined { @@ -1110,8 +1105,8 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: MultiChannelCCEndPointFindReport[], + _ctx: CCParsingContext, ): void { // Concat the list of end points this.foundEndpoints = [...partials, this] @@ -1119,9 +1114,9 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "generic device class": getGenericDeviceClass( this.genericClass, @@ -1147,12 +1142,11 @@ export interface MultiChannelCCEndPointFindOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelCCEndPointFindReport) export class MultiChannelCCEndPointFind extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCEndPointFindOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.genericClass = this.payload[0]; @@ -1166,14 +1160,14 @@ export class MultiChannelCCEndPointFind extends MultiChannelCC { public genericClass: number; public specificClass: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.genericClass, this.specificClass]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "generic device class": getGenericDeviceClass(this.genericClass).label, @@ -1189,10 +1183,9 @@ export class MultiChannelCCEndPointFind extends MultiChannelCC { @CCCommand(MultiChannelCommand.AggregatedMembersReport) export class MultiChannelCCAggregatedMembersReport extends MultiChannelCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.aggregatedEndpointIndex = this.payload[0] & 0b0111_1111; @@ -1211,9 +1204,9 @@ export class MultiChannelCCAggregatedMembersReport extends MultiChannelCC { ) public readonly members: readonly number[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "aggregated endpoint": this.aggregatedEndpointIndex, members: this.members.join(", "), @@ -1233,12 +1226,11 @@ export interface MultiChannelCCAggregatedMembersGetOptions @expectedCCResponse(MultiChannelCCAggregatedMembersReport) export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCAggregatedMembersGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1252,14 +1244,14 @@ export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { public requestedEndpoint: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.requestedEndpoint & 0b0111_1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { endpoint: this.requestedEndpoint }, }; } @@ -1319,16 +1311,15 @@ function testResponseForCommandEncapsulation( ) export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCCommandEncapsulationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); if ( - this.host.getDeviceConfig?.(this.nodeId as number)?.compat + options.context.getDeviceConfig?.(this.nodeId as number)?.compat ?.treatDestinationEndpointAsSource ) { // This device incorrectly uses the destination field to indicate the source endpoint @@ -1348,27 +1339,17 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { } } // No need to validate further, each CC does it for itself - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: this.payload.subarray(2), fromEncapsulation: true, encapCC: this, origin: options.origin, - frameType: options.frameType, + context: options.context, }); } else { this.encapsulated = options.encapsulated; options.encapsulated.encapsulatingCC = this as any; this.destination = options.destination; - - if ( - this.host.getDeviceConfig?.(this.nodeId as number)?.compat - ?.treatDestinationEndpointAsSource - ) { - // This device incorrectly responds from the endpoint we've passed as our source endpoint - if (typeof this.destination === "number") { - this.endpointIndex = this.destination; - } - } } } @@ -1376,7 +1357,17 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { /** The destination end point (0-127) or an array of destination end points (1-7) */ public destination: MultiChannelCCDestination; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { + if ( + ctx.getDeviceConfig?.(this.nodeId as number)?.compat + ?.treatDestinationEndpointAsSource + ) { + // This device incorrectly responds from the endpoint we've passed as our source endpoint + if (typeof this.destination === "number") { + this.endpointIndex = this.destination; + } + } + const destination = typeof this.destination === "number" // The destination is a single number ? this.destination & 0b0111_1111 @@ -1384,14 +1375,14 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { : encodeBitMask(this.destination, 7)[0] | 0b1000_0000; this.payload = Buffer.concat([ Buffer.from([this.endpointIndex & 0b0111_1111, destination]), - this.encapsulated.serialize(), + this.encapsulated.serialize(ctx), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { source: this.endpointIndex, destination: typeof this.destination === "number" @@ -1410,10 +1401,9 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { @CCCommand(MultiChannelCommand.ReportV1) export class MultiChannelCCV1Report extends MultiChannelCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); // V1 won't be extended in the future, so do an exact check validatePayload(this.payload.length === 2); this.requestedCC = this.payload[0]; @@ -1423,9 +1413,9 @@ export class MultiChannelCCV1Report extends MultiChannelCC { public readonly requestedCC: CommandClasses; public readonly endpointCount: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { CC: getCCName(this.requestedCC), "# of endpoints": this.endpointCount, @@ -1450,12 +1440,11 @@ export interface MultiChannelCCV1GetOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelCCV1Report, testResponseForMultiChannelV1Get) export class MultiChannelCCV1Get extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCV1GetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1469,14 +1458,14 @@ export class MultiChannelCCV1Get extends MultiChannelCC { public requestedCC: CommandClasses; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.requestedCC]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { CC: getCCName(this.requestedCC) }, }; } @@ -1514,12 +1503,11 @@ export interface MultiChannelCCV1CommandEncapsulationOptions ) export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiChannelCCV1CommandEncapsulationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.endpointIndex = this.payload[0]; @@ -1530,12 +1518,12 @@ export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { && this.payload[1] === 0x00; // No need to validate further, each CC does it for itself - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: this.payload.subarray(isV2withV1Header ? 2 : 1), fromEncapsulation: true, encapCC: this, origin: options.origin, - frameType: options.frameType, + context: options.context, }); } else { this.encapsulated = options.encapsulated; @@ -1546,12 +1534,12 @@ export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { public encapsulated!: CommandClass; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.endpointIndex]), - this.encapsulated.serialize(), + this.encapsulated.serialize(ctx), ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -1559,9 +1547,9 @@ export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { return super.computeEncapsulationOverhead() + 1; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { source: this.endpointIndex }, }; } diff --git a/packages/cc/src/cc/MultiCommandCC.ts b/packages/cc/src/cc/MultiCommandCC.ts index 039c4050c56f..5d620e0737e3 100644 --- a/packages/cc/src/cc/MultiCommandCC.ts +++ b/packages/cc/src/cc/MultiCommandCC.ts @@ -1,11 +1,11 @@ -import type { MessageOrCCLogEntry } from "@zwave-js/core/safe"; import { CommandClasses, EncapsulationFlags, type MaybeNotKnown, + type MessageOrCCLogEntry, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; import { @@ -45,12 +45,12 @@ export class MultiCommandCCAPI extends CCAPI { ); // FIXME: This should not be on the API but rather on the applHost level - const cc = new MultiCommandCCCommandEncapsulation(this.applHost, { + const cc = new MultiCommandCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, encapsulated: commands, }); cc.endpointIndex = this.endpoint.index; - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -68,10 +68,9 @@ export class MultiCommandCC extends CommandClass { } public static encapsulate( - host: ZWaveHost, CCs: CommandClass[], ): MultiCommandCCCommandEncapsulation { - const ret = new MultiCommandCCCommandEncapsulation(host, { + const ret = new MultiCommandCCCommandEncapsulation({ nodeId: CCs[0].nodeId, encapsulated: CCs, }); @@ -105,12 +104,11 @@ export interface MultiCommandCCCommandEncapsulationOptions // When sending commands encapsulated in this CC, responses to GET-type commands likely won't be encapsulated export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultiCommandCCCommandEncapsulationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const numCommands = this.payload[0]; @@ -121,7 +119,7 @@ export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { const cmdLength = this.payload[offset]; validatePayload(this.payload.length >= offset + 1 + cmdLength); this.encapsulated.push( - CommandClass.from(this.host, { + CommandClass.from({ data: this.payload.subarray( offset + 1, offset + 1 + cmdLength, @@ -129,7 +127,7 @@ export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { fromEncapsulation: true, encapCC: this, origin: options.origin, - frameType: options.frameType, + context: options.context, }), ); offset += 1 + cmdLength; @@ -144,21 +142,21 @@ export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { public encapsulated: CommandClass[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const buffers: Buffer[] = []; buffers.push(Buffer.from([this.encapsulated.length])); for (const cmd of this.encapsulated) { - const cmdBuffer = cmd.serialize(); + const cmdBuffer = cmd.serialize(ctx); buffers.push(Buffer.from([cmdBuffer.length])); buffers.push(cmdBuffer); } this.payload = Buffer.concat(buffers); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), // Hide the default payload line message: undefined, }; diff --git a/packages/cc/src/cc/MultilevelSensorCC.ts b/packages/cc/src/cc/MultilevelSensorCC.ts index 785723cf61bf..f19e272f2714 100644 --- a/packages/cc/src/cc/MultilevelSensorCC.ts +++ b/packages/cc/src/cc/MultilevelSensorCC.ts @@ -7,12 +7,16 @@ import { timespan, } from "@zwave-js/core"; import type { - IZWaveEndpoint, + ControlsCC, + EndpointId, + GetEndpoint, MessageOrCCLogEntry, MessageRecord, + NodeId, Scale, SinglecastCC, SupervisionResult, + SupportsCC, ValueID, } from "@zwave-js/core/safe"; import { @@ -26,9 +30,13 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetUserPreferences, + GetValueDB, + LogNode, } from "@zwave-js/host/safe"; import { num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -44,6 +52,10 @@ import { type CCResponsePredicate, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -105,16 +117,17 @@ export const MultilevelSensorCCValues = Object.freeze({ * followed by the most recently used scale, otherwile falls back to the first supported one. */ function getPreferredSensorScale( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetUserPreferences & LogNode, nodeId: number, endpointIndex: number, sensorType: number, supportedScales: readonly number[], ): number { + const preferences = ctx.getUserPreferences(); const sensor = getSensor(sensorType); // If the sensor type is unknown, we have no default. Use the user-provided scale or 0 if (!sensor) { - const preferred = applHost.options.preferences?.scales[sensorType]; + const preferred = preferences?.scales[sensorType]; // We cannot look up strings for unknown sensor types, so this must be a number or we use the fallback if (typeof preferred !== "number") return 0; return preferred; @@ -124,23 +137,23 @@ function getPreferredSensorScale( let preferred: number | string | undefined; // Named scales apply to multiple sensor types. To be able to override the scale for single types // we need to look at the preferences by sensor type first - preferred = applHost.options.preferences?.scales[sensorType]; + preferred = preferences?.scales[sensorType]; // If the scale is named, we can then try to use the named preference const scaleGroupName = sensor.scaleGroupName; if (preferred == undefined && scaleGroupName) { - preferred = applHost.options.preferences?.scales[scaleGroupName]; + preferred = preferences?.scales[scaleGroupName]; } // Then attempt reading the scale from the corresponding value if (preferred == undefined) { const sensorName = getSensorName(sensorType); const sensorValue = MultilevelSensorCCValues.value(sensorName); - const metadata = applHost + const metadata = ctx .tryGetValueDB(nodeId) ?.getMetadata(sensorValue.endpoint(endpointIndex)); const scale = metadata?.ccSpecific?.scale; if (typeof scale === "number" && supportedScales.includes(scale)) { preferred = scale; - applHost.controllerLog.logNode(nodeId, { + ctx.logNode(nodeId, { endpoint: endpointIndex, message: `No scale preference for sensor type ${sensorType}, using the last-used scale ${preferred}`, @@ -150,7 +163,7 @@ function getPreferredSensorScale( // Then fall back to the first supported scale if (preferred == undefined) { preferred = supportedScales[0] ?? 0; - applHost.controllerLog.logNode(nodeId, { + ctx.logNode(nodeId, { endpoint: endpointIndex, message: `No scale preference for sensor type ${sensorType}, using the first supported scale ${preferred}`, @@ -170,7 +183,7 @@ function getPreferredSensorScale( if (typeof preferred === "string") { // Looking up failed - applHost.controllerLog.logNode(nodeId, { + ctx.logNode(nodeId, { endpoint: endpointIndex, message: `Preferred scale "${preferred}" for sensor type ${sensorType} not found, using the first supported scale ${ @@ -185,7 +198,7 @@ function getPreferredSensorScale( // No info about supported scales, just use the preferred one return preferred; } else if (!supportedScales.includes(preferred)) { - applHost.controllerLog.logNode(nodeId, { + ctx.logNode(nodeId, { endpoint: endpointIndex, message: `Preferred scale ${preferred} not supported for sensor type ${sensorType}, using the first supported scale`, @@ -267,7 +280,7 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { }) ?? []; preferredScale = getPreferredSensorScale( - this.applHost, + this.host, this.endpoint.nodeId, this.endpoint.index, sensorType, @@ -275,13 +288,13 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { ); } - const cc = new MultilevelSensorCCGet(this.applHost, { + const cc = new MultilevelSensorCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sensorType, scale: scale ?? preferredScale, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSensorCCReport >( cc, @@ -318,11 +331,11 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { MultilevelSensorCommand.GetSupportedSensor, ); - const cc = new MultilevelSensorCCGetSupportedSensor(this.applHost, { + const cc = new MultilevelSensorCCGetSupportedSensor({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSensorCCSupportedSensorReport >( cc, @@ -340,12 +353,12 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { MultilevelSensorCommand.GetSupportedScale, ); - const cc = new MultilevelSensorCCGetSupportedScale(this.applHost, { + const cc = new MultilevelSensorCCGetSupportedScale({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sensorType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSensorCCSupportedScaleReport >( cc, @@ -365,14 +378,14 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { MultilevelSensorCommand.Report, ); - const cc = new MultilevelSensorCCReport(this.applHost, { + const cc = new MultilevelSensorCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, type: sensorType, scale, value, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -382,26 +395,28 @@ export class MultilevelSensorCCAPI extends PhysicalCCAPI { export class MultilevelSensorCC extends CommandClass { declare ccCommand: MultilevelSensorCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Multilevel Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (this.version >= 5) { + if (api.version >= 5) { // Query the supported sensor types - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving supported sensor types...", direction: "outbound", @@ -413,13 +428,13 @@ export class MultilevelSensorCC extends CommandClass { .map((t) => getSensorName(t)) .map((name) => `· ${name}`) .join("\n"); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported sensor types timed out, skipping interview...", @@ -431,7 +446,7 @@ export class MultilevelSensorCC extends CommandClass { // As well as the supported scales for each sensor for (const type of sensorTypes) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying supported scales for ${ getSensorName(type) @@ -449,13 +464,13 @@ export class MultilevelSensorCC extends CommandClass { ) .map((name) => `· ${name}`) .join("\n"); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported scales timed out, skipping interview...", @@ -466,27 +481,29 @@ export class MultilevelSensorCC extends CommandClass { } } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Multilevel Sensor"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); - if (this.version <= 4) { + if (api.version <= 4) { // Sensors up to V4 only support a single value - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current sensor reading...", direction: "outbound", @@ -502,7 +519,7 @@ sensor type: ${getSensorName(mlsResponse.type)} value: ${mlsResponse.value}${ sensorScale?.unit ? ` ${sensorScale.unit}` : "" }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -517,7 +534,7 @@ value: ${mlsResponse.value}${ }) || []; for (const type of sensorTypes) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying ${ getSensorName(type) @@ -530,7 +547,7 @@ value: ${mlsResponse.value}${ const logMessage = `received current ${ getSensorName(type) } sensor reading: ${value.value} ${value.scale.unit || ""}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -542,15 +559,21 @@ value: ${mlsResponse.value}${ public shouldRefreshValues( this: SinglecastCC, - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): boolean { // Poll the device when all of the supported values were last updated longer than 6 hours ago. // This may lead to some values not being updated, but the user may have disabled some unnecessary // reports to reduce traffic. - const valueDB = applHost.tryGetValueDB(this.nodeId); + const valueDB = ctx.tryGetValueDB(this.nodeId); if (!valueDB) return true; - const values = this.getDefinedValueIDs(applHost).filter((v) => + const values = this.getDefinedValueIDs(ctx).filter((v) => MultilevelSensorCCValues.value.is(v) ); return values.every((v) => { @@ -567,10 +590,10 @@ value: ${mlsResponse.value}${ * This only works AFTER the interview process */ public static getSupportedSensorTypesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( MultilevelSensorCCValues.supportedSensorTypes.endpoint( @@ -584,11 +607,11 @@ value: ${mlsResponse.value}${ * This only works AFTER the interview process */ public static getSupportedScalesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, sensorType: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( MultilevelSensorCCValues.supportedScales(sensorType).endpoint( @@ -598,7 +621,7 @@ value: ${mlsResponse.value}${ } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -607,7 +630,7 @@ value: ${mlsResponse.value}${ const sensor = getSensor(propertyKey); if (sensor) return sensor.label; } - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } @@ -622,12 +645,11 @@ export interface MultilevelSensorCCReportOptions extends CCCommandOptions { @useSupervision() export class MultilevelSensorCCReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSensorCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -647,8 +669,8 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const sensor = getSensor(this.type); const scale = getSensorScale( @@ -657,15 +679,17 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { ) ?? getUnknownScale(this.scale); // Filter out unknown sensor types and scales, unless the strict validation is disabled - const measurementValidation = !this.host.getDeviceConfig?.( + const measurementValidation = !ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.disableStrictMeasurementValidation; + const ccVersion = getEffectiveCCVersion(ctx, this); + if (measurementValidation) { // Filter out unsupported sensor types and scales if possible - if (this.version >= 5) { + if (ccVersion >= 5) { const supportedSensorTypes = this.getValue( - applHost, + ctx, MultilevelSensorCCValues.supportedSensorTypes, ); if (supportedSensorTypes?.length) { @@ -677,7 +701,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { } const supportedScales = this.getValue( - applHost, + ctx, MultilevelSensorCCValues.supportedScales(this.type), ); if (supportedScales?.length) { @@ -704,7 +728,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { const sensorName = getSensorName(this.type); const sensorValue = MultilevelSensorCCValues.value(sensorName); - this.setMetadata(applHost, sensorValue, { + this.setMetadata(ctx, sensorValue, { ...sensorValue.meta, unit: scale.unit, ccSpecific: { @@ -712,7 +736,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { scale: scale.key, }, }); - this.setValue(applHost, sensorValue, this.value); + this.setValue(ctx, sensorValue, this.value); return true; } @@ -721,17 +745,17 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { public scale: number; public value: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.type]), encodeFloatWithScale(this.value, this.scale), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "sensor type": getSensorName(this.type), scale: (getSensorScale(this.type, this.scale) @@ -767,12 +791,11 @@ export type MultilevelSensorCCGetOptions = ) export class MultilevelSensorCCGet extends MultilevelSensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSensorCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length >= 2) { this.sensorType = this.payload[0]; @@ -789,7 +812,7 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC { public sensorType: number | undefined; public scale: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if ( this.sensorType != undefined && this.scale != undefined @@ -799,10 +822,10 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC { (this.scale & 0b11) << 3, ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord = {}; if ( this.sensorType != undefined @@ -819,7 +842,7 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -837,12 +860,11 @@ export class MultilevelSensorCCSupportedSensorReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSensorCCSupportedSensorReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -856,14 +878,14 @@ export class MultilevelSensorCCSupportedSensorReport @ccValue(MultilevelSensorCCValues.supportedSensorTypes) public supportedSensorTypes: readonly number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeBitMask(this.supportedSensorTypes); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported sensor types": this.supportedSensorTypes .map((t) => `\n· ${getSensorName(t)}`) @@ -888,12 +910,11 @@ export interface MultilevelSensorCCSupportedScaleReportOptions @CCCommand(MultilevelSensorCommand.SupportedScaleReport) export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSensorCCSupportedScaleReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -917,17 +938,17 @@ export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { ) public readonly supportedScales: readonly number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.sensorType]), encodeBitMask(this.supportedScales, 4, 0), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "sensor type": getSensorName(this.sensorType), "supported scales": this.supportedScales @@ -955,12 +976,11 @@ export interface MultilevelSensorCCGetSupportedScaleOptions @expectedCCResponse(MultilevelSensorCCSupportedScaleReport) export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSensorCCGetSupportedScaleOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.sensorType = this.payload[0]; @@ -971,14 +991,14 @@ export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC { public sensorType: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sensorType]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "sensor type": getSensorName(this.sensorType), }, diff --git a/packages/cc/src/cc/MultilevelSwitchCC.ts b/packages/cc/src/cc/MultilevelSwitchCC.ts index cda49dc18438..a7a188d8e4f4 100644 --- a/packages/cc/src/cc/MultilevelSwitchCC.ts +++ b/packages/cc/src/cc/MultilevelSwitchCC.ts @@ -13,11 +13,7 @@ import { parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -35,6 +31,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -228,11 +228,11 @@ export class MultilevelSwitchCCAPI extends CCAPI { MultilevelSwitchCommand.Get, ); - const cc = new MultilevelSwitchCCGet(this.applHost, { + const cc = new MultilevelSwitchCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSwitchCCReport >( cc, @@ -259,13 +259,13 @@ export class MultilevelSwitchCCAPI extends CCAPI { MultilevelSwitchCommand.Set, ); - const cc = new MultilevelSwitchCCSet(this.applHost, { + const cc = new MultilevelSwitchCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, targetValue, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -277,13 +277,13 @@ export class MultilevelSwitchCCAPI extends CCAPI { MultilevelSwitchCommand.StartLevelChange, ); - const cc = new MultilevelSwitchCCStartLevelChange(this.applHost, { + const cc = new MultilevelSwitchCCStartLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async stopLevelChange(): Promise { @@ -292,12 +292,12 @@ export class MultilevelSwitchCCAPI extends CCAPI { MultilevelSwitchCommand.StopLevelChange, ); - const cc = new MultilevelSwitchCCStopLevelChange(this.applHost, { + const cc = new MultilevelSwitchCCStopLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupported(): Promise> { @@ -306,11 +306,11 @@ export class MultilevelSwitchCCAPI extends CCAPI { MultilevelSwitchCommand.SupportedGet, ); - const cc = new MultilevelSwitchCCSupportedGet(this.applHost, { + const cc = new MultilevelSwitchCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSwitchCCSupportedReport >( cc, @@ -468,7 +468,7 @@ export class MultilevelSwitchCCAPI extends CCAPI { ); // and optimistically update the currentValue for (const node of affectedNodes) { - this.applHost + this.host .tryGetValueDB(node.id) ?.setValue(currentValueValueId, value); } @@ -518,33 +518,35 @@ export class MultilevelSwitchCCAPI extends CCAPI { export class MultilevelSwitchCC extends CommandClass { declare ccCommand: MultilevelSwitchCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Multilevel Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (this.version >= 3) { + if (api.version >= 3) { // Find out which kind of switch this is - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting switch type...", direction: "outbound", }); const switchType = await api.getSupported(); if (switchType != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `has switch type ${ getEnumMemberName( @@ -558,27 +560,29 @@ export class MultilevelSwitchCC extends CommandClass { } else { // requesting the switch type automatically creates the up/down actions // We need to do this manually for V1 and V2 - this.createMetadataForLevelChangeActions(applHost); + this.createMetadataForLevelChangeActions(ctx); } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Multilevel Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "requesting current switch state...", direction: "outbound", @@ -587,24 +591,24 @@ export class MultilevelSwitchCC extends CommandClass { } public setMappedBasicValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, value: number, ): boolean { - this.setValue(applHost, MultilevelSwitchCCValues.currentValue, value); + this.setValue(ctx, MultilevelSwitchCCValues.currentValue, value); return true; } protected createMetadataForLevelChangeActions( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, // SDS13781: The Primary Switch Type SHOULD be 0x02 (Up/Down) switchType: SwitchType = SwitchType["Down/Up"], ): void { this.ensureMetadata( - applHost, + ctx, MultilevelSwitchCCValues.levelChangeUp(switchType), ); this.ensureMetadata( - applHost, + ctx, MultilevelSwitchCCValues.levelChangeDown(switchType), ); } @@ -621,12 +625,11 @@ export interface MultilevelSwitchCCSetOptions extends CCCommandOptions { @useSupervision() export class MultilevelSwitchCCSet extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSwitchCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.targetValue = this.payload[0]; @@ -643,14 +646,15 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { public targetValue: number; public duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.targetValue, (this.duration ?? Duration.default()).serializeSet(), ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -658,10 +662,10 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { this.payload = this.payload.subarray(0, 1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "target value": this.targetValue, }; @@ -669,7 +673,7 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -685,12 +689,11 @@ export interface MultilevelSwitchCCReportOptions extends CCCommandOptions { @CCCommand(MultilevelSwitchCommand.Report) export class MultilevelSwitchCCReport extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | MultilevelSwitchCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -699,7 +702,7 @@ export class MultilevelSwitchCCReport extends MultilevelSwitchCC { this.payload[0] === 0xff ? 99 : parseMaybeNumber(this.payload[0]); - if (this.version >= 4 && this.payload.length >= 3) { + if (this.payload.length >= 3) { this.targetValue = parseMaybeNumber(this.payload[1]); this.duration = Duration.parseReport(this.payload[2]); } @@ -719,16 +722,16 @@ export class MultilevelSwitchCCReport extends MultilevelSwitchCC { @ccValue(MultilevelSwitchCCValues.currentValue) public currentValue: MaybeUnknown | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.currentValue ?? 0xfe, this.targetValue ?? 0xfe, (this.duration ?? Duration.default()).serializeReport(), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "current value": maybeUnknownToString(this.currentValue), }; @@ -737,7 +740,7 @@ export class MultilevelSwitchCCReport extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -771,12 +774,11 @@ export type MultilevelSwitchCCStartLevelChangeOptions = @useSupervision() export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & MultilevelSwitchCCStartLevelChangeOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5; @@ -802,7 +804,7 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { public ignoreStartLevel: boolean; public direction: keyof typeof LevelChangeDirection; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const controlByte = (LevelChangeDirection[this.direction] << 6) | (this.ignoreStartLevel ? 0b0010_0000 : 0); this.payload = Buffer.from([ @@ -811,8 +813,9 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { (this.duration ?? Duration.default()).serializeSet(), ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -820,10 +823,10 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { this.payload = this.payload.subarray(0, -1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { startLevel: `${this.startLevel}${ this.ignoreStartLevel ? " (ignored)" : "" @@ -834,7 +837,7 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -852,12 +855,11 @@ export interface MultilevelSwitchCCSupportedReportOptions { @CCCommand(MultilevelSwitchCommand.SupportedReport) export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & MultilevelSwitchCCSupportedReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -872,20 +874,20 @@ export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { @ccValue(MultilevelSwitchCCValues.switchType) public readonly switchType: SwitchType; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; - this.createMetadataForLevelChangeActions(applHost, this.switchType); + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + this.createMetadataForLevelChangeActions(ctx, this.switchType); return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.switchType & 0b11111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "switch type": getEnumMemberName(SwitchType, this.switchType), }, diff --git a/packages/cc/src/cc/NoOperationCC.ts b/packages/cc/src/cc/NoOperationCC.ts index a83bb194e56e..c81d89f72737 100644 --- a/packages/cc/src/cc/NoOperationCC.ts +++ b/packages/cc/src/cc/NoOperationCC.ts @@ -15,8 +15,8 @@ import { isCommandClassContainer } from "../lib/ICommandClassContainer"; @API(CommandClasses["No Operation"]) export class NoOperationCCAPI extends PhysicalCCAPI { public async send(): Promise { - await this.applHost.sendCommand( - new NoOperationCC(this.applHost, { + await this.host.sendCommand( + new NoOperationCC({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }), diff --git a/packages/cc/src/cc/NodeNamingCC.ts b/packages/cc/src/cc/NodeNamingCC.ts index 53ebeb26d04a..78a52401ffa3 100644 --- a/packages/cc/src/cc/NodeNamingCC.ts +++ b/packages/cc/src/cc/NodeNamingCC.ts @@ -9,11 +9,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -29,6 +25,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -133,11 +131,11 @@ export class NodeNamingAndLocationCCAPI extends PhysicalCCAPI { NodeNamingAndLocationCommand.NameGet, ); - const cc = new NodeNamingAndLocationCCNameGet(this.applHost, { + const cc = new NodeNamingAndLocationCCNameGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NodeNamingAndLocationCCNameReport >( cc, @@ -153,12 +151,12 @@ export class NodeNamingAndLocationCCAPI extends PhysicalCCAPI { NodeNamingAndLocationCommand.NameSet, ); - const cc = new NodeNamingAndLocationCCNameSet(this.applHost, { + const cc = new NodeNamingAndLocationCCNameSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, name, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getLocation(): Promise> { @@ -167,11 +165,11 @@ export class NodeNamingAndLocationCCAPI extends PhysicalCCAPI { NodeNamingAndLocationCommand.LocationGet, ); - const cc = new NodeNamingAndLocationCCLocationGet(this.applHost, { + const cc = new NodeNamingAndLocationCCLocationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NodeNamingAndLocationCCLocationReport >( cc, @@ -189,12 +187,12 @@ export class NodeNamingAndLocationCCAPI extends PhysicalCCAPI { NodeNamingAndLocationCommand.LocationSet, ); - const cc = new NodeNamingAndLocationCCLocationSet(this.applHost, { + const cc = new NodeNamingAndLocationCCLocationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, location, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -209,51 +207,55 @@ export class NodeNamingAndLocationCC extends CommandClass { return true; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Node Naming and Location"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "retrieving node name...", direction: "outbound", }); const name = await api.getName(); if (name != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `is named "${name}"`, direction: "inbound", }); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "retrieving node location...", direction: "outbound", }); const location = await api.getLocation(); if (location != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `received location: ${location}`, direction: "inbound", }); @@ -272,12 +274,11 @@ export interface NodeNamingAndLocationCCNameSetOptions @useSupervision() export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | NodeNamingAndLocationCCNameSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -291,7 +292,7 @@ export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { public name: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const encoding = isASCII(this.name) ? "ascii" : "utf16le"; this.payload = Buffer.allocUnsafe( 1 + this.name.length * (encoding === "ascii" ? 1 : 2), @@ -309,12 +310,12 @@ export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { 0, Math.min(16, nameAsBuffer.length), ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { name: this.name }, }; } @@ -323,10 +324,9 @@ export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { @CCCommand(NodeNamingAndLocationCommand.NameReport) export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); const encoding = this.payload[0] === 2 ? "utf16le" : "ascii"; let nameBuffer = this.payload.subarray(1); if (encoding === "utf16le") { @@ -340,9 +340,9 @@ export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { @ccValue(NodeNamingAndLocationCCValues.name) public readonly name: string; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { name: this.name }, }; } @@ -365,12 +365,11 @@ export class NodeNamingAndLocationCCLocationSet extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | NodeNamingAndLocationCCLocationSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -384,7 +383,7 @@ export class NodeNamingAndLocationCCLocationSet public location: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const encoding = isASCII(this.location) ? "ascii" : "utf16le"; this.payload = Buffer.allocUnsafe( 1 + this.location.length * (encoding === "ascii" ? 1 : 2), @@ -402,12 +401,12 @@ export class NodeNamingAndLocationCCLocationSet 0, Math.min(16, locationAsBuffer.length), ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { location: this.location }, }; } @@ -418,10 +417,9 @@ export class NodeNamingAndLocationCCLocationReport extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); const encoding = this.payload[0] === 2 ? "utf16le" : "ascii"; let locationBuffer = this.payload.subarray(1); if (encoding === "utf16le") { @@ -435,9 +433,9 @@ export class NodeNamingAndLocationCCLocationReport @ccValue(NodeNamingAndLocationCCValues.location) public readonly location: string; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { location: this.location }, }; } diff --git a/packages/cc/src/cc/NotificationCC.ts b/packages/cc/src/cc/NotificationCC.ts index f05ac12c7d0e..f0704a5d06cf 100644 --- a/packages/cc/src/cc/NotificationCC.ts +++ b/packages/cc/src/cc/NotificationCC.ts @@ -11,15 +11,18 @@ import { } from "@zwave-js/core"; import { CommandClasses, + type ControlsCC, Duration, - type IZWaveEndpoint, - type IZWaveNode, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, type SinglecastCC, type SupervisionResult, + type SupportsCC, type ValueID, ValueMetadata, type ValueMetadataNumeric, @@ -32,9 +35,12 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, + LogNode, } from "@zwave-js/host/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -50,7 +56,11 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, InvalidCC, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -213,10 +223,10 @@ export const NotificationCCValues = Object.freeze({ }); function shouldAutoCreateSimpleDoorSensorValue( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost.tryGetValueDB(endpoint.nodeId); + const valueDB = ctx.tryGetValueDB(endpoint.nodeId); if (!valueDB) return false; const supportedACEvents = valueDB.getValue( NotificationCCValues.supportedNotificationEvents( @@ -290,12 +300,12 @@ export class NotificationCCAPI extends PhysicalCCAPI { NotificationCommand.Get, ); - const cc = new NotificationCCGet(this.applHost, { + const cc = new NotificationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand( + return this.host.sendCommand( cc, this.commandOptions, ); @@ -310,12 +320,12 @@ export class NotificationCCAPI extends PhysicalCCAPI { NotificationCommand.Report, ); - const cc = new NotificationCCReport(this.applHost, { + const cc = new NotificationCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -327,7 +337,6 @@ export class NotificationCCAPI extends PhysicalCCAPI { "notificationStatus", "notificationEvent", "alarmLevel", - "zensorNetSourceNodeId", "eventParameters", "sequenceNumber", ]); @@ -344,13 +353,13 @@ export class NotificationCCAPI extends PhysicalCCAPI { NotificationCommand.Set, ); - const cc = new NotificationCCSet(this.applHost, { + const cc = new NotificationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, notificationType, notificationStatus, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -360,11 +369,11 @@ export class NotificationCCAPI extends PhysicalCCAPI { NotificationCommand.SupportedGet, ); - const cc = new NotificationCCSupportedGet(this.applHost, { + const cc = new NotificationCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NotificationCCSupportedReport >( cc, @@ -387,12 +396,12 @@ export class NotificationCCAPI extends PhysicalCCAPI { NotificationCommand.EventSupportedGet, ); - const cc = new NotificationCCEventSupportedGet(this.applHost, { + const cc = new NotificationCCEventSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, notificationType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NotificationCCEventSupportedReport >( cc, @@ -490,11 +499,11 @@ export class NotificationCC extends CommandClass { } private async determineNotificationMode( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetNode & LogNode, api: NotificationCCAPI, supportedNotificationEvents: ReadonlyMap, ): Promise<"push" | "pull"> { - const node = this.getNode(applHost)!; + const node = this.getNode(ctx)!; // SDS14223: If the supporting node does not support the Association Command Class, // it may be concluded that the supporting node implements Pull Mode and discovery may be aborted. @@ -504,7 +513,7 @@ export class NotificationCC extends CommandClass { try { const groupsIssueingNotifications = AssociationGroupInfoCC .findGroupsForIssuedCommand( - applHost, + ctx, node, this.ccId, NotificationCommand.Report, @@ -515,7 +524,7 @@ export class NotificationCC extends CommandClass { } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `determining whether this node is pull or push...`, direction: "outbound", @@ -551,26 +560,28 @@ export class NotificationCC extends CommandClass { /** Whether the node implements push or pull notifications */ public static getNotificationMode( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + node: NodeId, ): MaybeNotKnown<"push" | "pull"> { - return applHost + return ctx .getValueDB(node.id) .getValue(NotificationCCValues.notificationMode.id); } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Notification, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -580,13 +591,13 @@ export class NotificationCC extends CommandClass { // we must associate ourselves with that channel try { await ccUtils.assignLifelineIssueingCommand( - applHost, + ctx, endpoint, this.ccId, NotificationCommand.Report, ); } catch { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Configuring associations to receive ${ getCCName( @@ -598,8 +609,8 @@ export class NotificationCC extends CommandClass { } let supportsV1Alarm = false; - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported notification types...", direction: "outbound", @@ -607,7 +618,7 @@ export class NotificationCC extends CommandClass { const suppResponse = await api.getSupported(); if (!suppResponse) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported notification types timed out, skipping interview...", @@ -631,19 +642,19 @@ export class NotificationCC extends CommandClass { .map((name) => `\n· ${name}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); - if (this.version >= 3) { + if (api.version >= 3) { // Query each notification for its supported events for (let i = 0; i < supportedNotificationTypes.length; i++) { const type = supportedNotificationTypes[i]; const name = supportedNotificationNames[i]; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying supported notification events for ${name}...`, @@ -652,7 +663,7 @@ export class NotificationCC extends CommandClass { const supportedEvents = await api.getSupportedEvents(type); if (supportedEvents) { supportedNotificationEvents.set(type, supportedEvents); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received supported notification events for ${name}: ${ @@ -668,24 +679,24 @@ export class NotificationCC extends CommandClass { // Determine whether the node is a push or pull node let notificationMode = this.getValue<"push" | "pull">( - applHost, + ctx, NotificationCCValues.notificationMode, ); if (notificationMode !== "push" && notificationMode !== "pull") { notificationMode = await this.determineNotificationMode( - applHost, + ctx, api, supportedNotificationEvents, ); this.setValue( - applHost, + ctx, NotificationCCValues.notificationMode, notificationMode, ); } if (notificationMode === "pull") { - await this.refreshValues(applHost); + await this.refreshValues(ctx); } /* if (notificationMode === "push") */ else { for (let i = 0; i < supportedNotificationTypes.length; i++) { const type = supportedNotificationTypes[i]; @@ -693,7 +704,7 @@ export class NotificationCC extends CommandClass { const notification = getNotification(type); // Enable reports for each notification type - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `enabling notifications for ${name}...`, direction: "outbound", @@ -725,11 +736,11 @@ export class NotificationCC extends CommandClass { // * do this only if the last update was more than 5 minutes ago // * schedule an auto-idle if the last update was less than 5 minutes ago but before the current applHost start if ( - this.getValue(applHost, value) + this.getValue(ctx, value) == undefined ) { this.setValue( - applHost, + ctx, value, 0, /* idle */ ); @@ -743,13 +754,13 @@ export class NotificationCC extends CommandClass { } // Only create metadata for V1 values if necessary - if (this.version === 1 || supportsV1Alarm) { - this.ensureMetadata(applHost, NotificationCCValues.alarmType); - this.ensureMetadata(applHost, NotificationCCValues.alarmLevel); + if (api.version === 1 || supportsV1Alarm) { + this.ensureMetadata(ctx, NotificationCCValues.alarmType); + this.ensureMetadata(ctx, NotificationCCValues.alarmLevel); } // Also create metadata for values mapped through compat config - const mappings = applHost.getDeviceConfig?.(this.nodeId as number) + const mappings = ctx.getDeviceConfig?.(this.nodeId as number) ?.compat?.alarmMapping; if (mappings) { // Find all mappings to a valid notification variable @@ -783,11 +794,11 @@ export class NotificationCC extends CommandClass { // Create or update the metadata const metadata = getNotificationValueMetadata( - this.getMetadata(applHost, notificationValue), + this.getMetadata(ctx, notificationValue), notification, valueConfig, ); - this.setMetadata(applHost, notificationValue, metadata); + this.setMetadata(ctx, notificationValue, metadata); // Set the value to idle if it has no value yet if (valueConfig.idle) { @@ -795,10 +806,10 @@ export class NotificationCC extends CommandClass { // * do this only if the last update was more than 5 minutes ago // * schedule an auto-idle if the last update was less than 5 minutes ago but before the current applHost start if ( - this.getValue(applHost, notificationValue) == undefined + this.getValue(ctx, notificationValue) == undefined ) { this.setValue( - applHost, + ctx, notificationValue, 0, /* idle */ ); @@ -808,13 +819,13 @@ export class NotificationCC extends CommandClass { // Remember supported notification types and events in the cache this.setValue( - applHost, + ctx, NotificationCCValues.supportedNotificationTypes, [...supportedNotifications.keys()], ); for (const [type, events] of supportedNotifications) { this.setValue( - applHost, + ctx, NotificationCCValues.supportedNotificationEvents(type), [...events], ); @@ -822,17 +833,19 @@ export class NotificationCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; // Refreshing values only works on pull nodes - if (NotificationCC.getNotificationMode(applHost, node) === "pull") { - const endpoint = this.getEndpoint(applHost)!; + if (NotificationCC.getNotificationMode(ctx, node) === "pull") { + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Notification, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, @@ -840,7 +853,7 @@ export class NotificationCC extends CommandClass { // Load supported notification types and events from cache const supportedNotificationTypes = this.getValue( - applHost, + ctx, NotificationCCValues.supportedNotificationTypes, ) ?? []; const supportedNotificationNames = supportedNotificationTypes.map( @@ -852,7 +865,7 @@ export class NotificationCC extends CommandClass { const name = supportedNotificationNames[i]; // Always query each notification for its current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying notification status for ${name}...`, direction: "outbound", @@ -870,7 +883,7 @@ export class NotificationCC extends CommandClass { // Remember when we did this this.setValue( - applHost, + ctx, NotificationCCValues.lastRefresh, Date.now(), ); @@ -879,18 +892,24 @@ export class NotificationCC extends CommandClass { public shouldRefreshValues( this: SinglecastCC, - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): boolean { // Pull-mode nodes must be polled regularly const isPullMode = NotificationCC.getNotificationMode( - applHost, - this.getNode(applHost)!, + ctx, + this.getNode(ctx)!, ) === "pull"; if (!isPullMode) return false; const lastUpdated = this.getValue( - applHost, + ctx, NotificationCCValues.lastRefresh, ); @@ -911,10 +930,9 @@ export interface NotificationCCSetOptions extends CCCommandOptions { @useSupervision() export class NotificationCCSet extends NotificationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | NotificationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.notificationType = this.payload[0]; @@ -927,17 +945,17 @@ export class NotificationCCSet extends NotificationCC { public notificationType: number; public notificationStatus: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.notificationType, this.notificationStatus ? 0xff : 0x00, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "notification type": getNotificationName(this.notificationType), status: this.notificationStatus, @@ -963,24 +981,18 @@ export type NotificationCCReportOptions = @useSupervision() export class NotificationCCReport extends NotificationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (NotificationCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.alarmType = this.payload[0]; this.alarmLevel = this.payload[1]; - // V2..V3, reserved in V4+ - if ( - (this.version === 2 || this.version === 3) - && this.payload.length >= 3 - ) { - this.zensorNetSourceNodeId = this.payload[2]; - } + // Byte 2 used to be zensorNetSourceNodeId in V2 and V3, but we don't care about that + // V2+ requires the alarm bytes to be zero. Manufacturers don't care though, so we don't enforce that. // Don't use the version to decide because we might discard notifications // before the interview is complete @@ -1011,8 +1023,6 @@ export class NotificationCCReport extends NotificationCC { if ("alarmType" in options) { this.alarmType = options.alarmType; this.alarmLevel = options.alarmLevel; - // Send a V1 command - this.version = 1; } else { this.notificationType = options.notificationType; this.notificationStatus = true; @@ -1023,8 +1033,10 @@ export class NotificationCCReport extends NotificationCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + + const ccVersion = getEffectiveCCVersion(ctx, this); // Check if we need to re-interpret the alarm values somehow if ( @@ -1032,12 +1044,12 @@ export class NotificationCCReport extends NotificationCC { && this.alarmLevel != undefined && this.alarmType !== 0 ) { - if (this.version >= 2) { + if (ccVersion >= 2) { // Check if the device actually supports Notification CC, but chooses // to send Alarm frames instead (GH#1034) const supportedNotificationTypes = this.getValue< readonly number[] - >(applHost, NotificationCCValues.supportedNotificationTypes); + >(ctx, NotificationCCValues.supportedNotificationTypes); if ( isArray(supportedNotificationTypes) && supportedNotificationTypes.includes(this.alarmType) @@ -1045,7 +1057,7 @@ export class NotificationCCReport extends NotificationCC { const supportedNotificationEvents = this.getValue< readonly number[] >( - applHost, + ctx, NotificationCCValues.supportedNotificationEvents( this.alarmType, ), @@ -1055,7 +1067,7 @@ export class NotificationCCReport extends NotificationCC { && supportedNotificationEvents.includes(this.alarmLevel) ) { // This alarm frame corresponds to a valid notification event - applHost.controllerLog.logNode( + ctx.logNode( this.nodeId as number, `treating V1 Alarm frame as Notification Report`, ); @@ -1067,7 +1079,7 @@ export class NotificationCCReport extends NotificationCC { } } else { // V1 Alarm, check if there is a compat option to map this V1 report to a V2+ report - const mapping = this.host.getDeviceConfig?.( + const mapping = ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.alarmMapping; const match = mapping?.find( @@ -1077,7 +1089,7 @@ export class NotificationCCReport extends NotificationCC { || m.from.alarmLevel === this.alarmLevel), ); if (match) { - applHost.controllerLog.logNode( + ctx.logNode( this.nodeId as number, `compat mapping found, treating V1 Alarm frame as Notification Report`, ); @@ -1104,17 +1116,17 @@ export class NotificationCCReport extends NotificationCC { } // Now we can interpret the event parameters and turn them into something useful - this.parseEventParameters(applHost); + this.parseEventParameters(ctx); if (this.alarmType != undefined) { const alarmTypeValue = NotificationCCValues.alarmType; - this.ensureMetadata(applHost, alarmTypeValue); - this.setValue(applHost, alarmTypeValue, this.alarmType); + this.ensureMetadata(ctx, alarmTypeValue); + this.setValue(ctx, alarmTypeValue, this.alarmType); } if (this.alarmLevel != undefined) { const alarmLevelValue = NotificationCCValues.alarmLevel; - this.ensureMetadata(applHost, alarmLevelValue); - this.setValue(applHost, alarmLevelValue, this.alarmLevel); + this.ensureMetadata(ctx, alarmLevelValue); + this.setValue(ctx, alarmLevelValue, this.alarmLevel); } return true; @@ -1127,7 +1139,6 @@ export class NotificationCCReport extends NotificationCC { public notificationStatus: boolean | number | undefined; public notificationEvent: number | undefined; - public readonly zensorNetSourceNodeId: number | undefined; public eventParameters: | Buffer | Duration @@ -1137,7 +1148,7 @@ export class NotificationCCReport extends NotificationCC { public sequenceNumber: number | undefined; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord = {}; if (this.alarmType) { message = { @@ -1181,9 +1192,6 @@ export class NotificationCCReport extends NotificationCC { }; } } - if (this.zensorNetSourceNodeId) { - message["zensor net source node id"] = this.zensorNetSourceNodeId; - } if (this.sequenceNumber != undefined) { message["sequence number"] = this.sequenceNumber; } @@ -1227,12 +1235,12 @@ export class NotificationCCReport extends NotificationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } - private parseEventParameters(applHost: ZWaveApplicationHost): void { + private parseEventParameters(ctx: LogNode): void { // This only makes sense for V2+ notifications if ( this.notificationType == undefined @@ -1281,10 +1289,12 @@ export class NotificationCCReport extends NotificationCC { // Try to parse the event parameters - if this fails, we should still handle the notification report try { // Convert CommandClass instances to a standardized object representation - const cc = CommandClass.from(this.host, { + const cc = CommandClass.from({ data: this.eventParameters, fromEncapsulation: true, encapCC: this, + // FIXME: persistValues needs access to the CCParsingContext + context: {} as any, }); validatePayload(!(cc instanceof InvalidCC)); @@ -1327,7 +1337,7 @@ export class NotificationCCReport extends NotificationCC { userId: this.eventParameters[2], }; } else { - applHost.controllerLog.logNode( + ctx.logNode( this.nodeId as number, `Failed to parse Notification CC event parameters, ignoring them...`, "error", @@ -1371,22 +1381,13 @@ export class NotificationCCReport extends NotificationCC { } } - public serialize(): Buffer { - if (this.version === 1) { - if (this.alarmLevel == undefined || this.alarmType == undefined) { - throw new ZWaveError( - `Notification CC V1 (Alarm CC) reports requires the alarm type and level to be set!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.payload = Buffer.from([this.alarmType, this.alarmLevel]); - } else { + public serialize(ctx: CCEncodingContext): Buffer { + if (this.notificationType != undefined) { if ( - this.notificationType == undefined - || this.notificationEvent == undefined + this.notificationEvent == undefined ) { throw new ZWaveError( - `Notification CC reports requires the notification type and event to be set!`, + `Notification CC reports requires the notification event to be set!`, ZWaveErrorCodes.Argument_Invalid, ); } else if ( @@ -1398,6 +1399,7 @@ export class NotificationCCReport extends NotificationCC { ZWaveErrorCodes.Argument_Invalid, ); } + const controlByte = (this.sequenceNumber != undefined ? 0b1000_0000 : 0) | ((this.eventParameters?.length ?? 0) & 0b11111); @@ -1422,8 +1424,14 @@ export class NotificationCCReport extends NotificationCC { Buffer.from([this.sequenceNumber]), ]); } + } else { + this.payload = Buffer.from([ + this.alarmType ?? 0x00, + this.alarmLevel ?? 0x00, + ]); } - return super.serialize(); + + return super.serialize(ctx); } } @@ -1444,10 +1452,9 @@ export type NotificationCCGetOptions = @expectedCCResponse(NotificationCCReport) export class NotificationCCGet extends NotificationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | NotificationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.alarmType = this.payload[0] || undefined; @@ -1473,23 +1480,19 @@ export class NotificationCCGet extends NotificationCC { public notificationType: number | undefined; public notificationEvent: number | undefined; - public serialize(): Buffer { - const payload: number[] = [this.alarmType || 0]; - if (this.version >= 2 && this.notificationType != undefined) { - payload.push(this.notificationType); - if (this.version >= 3) { - payload.push( - this.notificationType === 0xff - ? 0x00 - : this.notificationEvent || 0, - ); - } - } - this.payload = Buffer.from(payload); - return super.serialize(); + public serialize(ctx: CCEncodingContext): Buffer { + const notificationEvent = this.notificationEvent === 0xff + ? 0x00 + : this.notificationEvent; + this.payload = Buffer.from([ + this.alarmType ?? 0x00, + this.notificationType ?? 0xff, + notificationEvent ?? 0x00, + ]); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.alarmType != undefined) { message["V1 alarm type"] = this.alarmType; @@ -1506,7 +1509,7 @@ export class NotificationCCGet extends NotificationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1521,12 +1524,11 @@ export interface NotificationCCSupportedReportOptions extends CCCommandOptions { @CCCommand(NotificationCommand.SupportedReport) export class NotificationCCSupportedReport extends NotificationCC { public constructor( - host: ZWaveHost, options: | NotificationCCSupportedReportOptions | CommandClassDeserializationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -1558,7 +1560,7 @@ export class NotificationCCSupportedReport extends NotificationCC { @ccValue(NotificationCCValues.supportedNotificationTypes) public supportedNotificationTypes: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const bitMask = encodeBitMask( this.supportedNotificationTypes, Math.max(...this.supportedNotificationTypes), @@ -1570,12 +1572,12 @@ export class NotificationCCSupportedReport extends NotificationCC { ]), bitMask, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supports V1 alarm": this.supportsV1Alarm, "supported notification types": this.supportedNotificationTypes @@ -1603,12 +1605,11 @@ export interface NotificationCCEventSupportedReportOptions @CCCommand(NotificationCommand.EventSupportedReport) export class NotificationCCEventSupportedReport extends NotificationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | NotificationCCEventSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -1633,12 +1634,12 @@ export class NotificationCCEventSupportedReport extends NotificationCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Store which events this notification supports this.setValue( - applHost, + ctx, NotificationCCValues.supportedNotificationEvents( this.notificationType, ), @@ -1651,7 +1652,7 @@ export class NotificationCCEventSupportedReport extends NotificationCC { if (!notification) { // This is an unknown notification this.setMetadata( - applHost, + ctx, NotificationCCValues.unknownNotificationType( this.notificationType, ), @@ -1673,12 +1674,12 @@ export class NotificationCCEventSupportedReport extends NotificationCC { const metadata = getNotificationValueMetadata( isFirst ? undefined - : this.getMetadata(applHost, notificationValue), + : this.getMetadata(ctx, notificationValue), notification, valueConfig, ); - this.setMetadata(applHost, notificationValue, metadata); + this.setMetadata(ctx, notificationValue, metadata); isFirst = false; } @@ -1691,7 +1692,7 @@ export class NotificationCCEventSupportedReport extends NotificationCC { public notificationType: number; public supportedEvents: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.notificationType, 0]); if (this.supportedEvents.length > 0) { const bitMask = encodeBitMask( @@ -1703,12 +1704,12 @@ export class NotificationCCEventSupportedReport extends NotificationCC { this.payload = Buffer.concat([this.payload, bitMask]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "notification type": getNotificationName(this.notificationType), "supported events": this.supportedEvents @@ -1738,12 +1739,11 @@ export interface NotificationCCEventSupportedGetOptions @expectedCCResponse(NotificationCCEventSupportedReport) export class NotificationCCEventSupportedGet extends NotificationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | NotificationCCEventSupportedGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.notificationType = this.payload[0]; @@ -1754,14 +1754,14 @@ export class NotificationCCEventSupportedGet extends NotificationCC { public notificationType: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.notificationType]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "notification type": getNotificationName(this.notificationType), }, diff --git a/packages/cc/src/cc/PowerlevelCC.ts b/packages/cc/src/cc/PowerlevelCC.ts index 044a6876eee7..49e461caa8d7 100644 --- a/packages/cc/src/cc/PowerlevelCC.ts +++ b/packages/cc/src/cc/PowerlevelCC.ts @@ -9,7 +9,7 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { PhysicalCCAPI } from "../lib/API"; @@ -53,12 +53,12 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { public async setNormalPowerlevel(): Promise { this.assertSupportsCommand(PowerlevelCommand, PowerlevelCommand.Set); - const cc = new PowerlevelCCSet(this.applHost, { + const cc = new PowerlevelCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, powerlevel: Powerlevel["Normal Power"], }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -68,13 +68,13 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(PowerlevelCommand, PowerlevelCommand.Set); - const cc = new PowerlevelCCSet(this.applHost, { + const cc = new PowerlevelCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, powerlevel, timeout, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getPowerlevel(): Promise< @@ -82,11 +82,11 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { > { this.assertSupportsCommand(PowerlevelCommand, PowerlevelCommand.Get); - const cc = new PowerlevelCCGet(this.applHost, { + const cc = new PowerlevelCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -101,12 +101,12 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { ): Promise { this.assertSupportsCommand(PowerlevelCommand, PowerlevelCommand.Report); - const cc = new PowerlevelCCReport(this.applHost, { + const cc = new PowerlevelCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -126,7 +126,7 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { ZWaveErrorCodes.Argument_Invalid, ); } - const testNode = this.applHost.nodes.getOrThrow(testNodeId); + const testNode = this.host.getNodeOrThrow(testNodeId); if (testNode.isFrequentListening) { throw new ZWaveError( `Node ${testNodeId} is FLiRS and therefore cannot be used for a powerlevel test.`, @@ -140,14 +140,14 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { ); } - const cc = new PowerlevelCCTestNodeSet(this.applHost, { + const cc = new PowerlevelCCTestNodeSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, testNodeId, powerlevel, testFrameCount, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getNodeTestStatus(): Promise< @@ -163,11 +163,11 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { PowerlevelCommand.TestNodeGet, ); - const cc = new PowerlevelCCTestNodeGet(this.applHost, { + const cc = new PowerlevelCCTestNodeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< PowerlevelCCTestNodeReport >( cc, @@ -191,12 +191,12 @@ export class PowerlevelCCAPI extends PhysicalCCAPI { PowerlevelCommand.TestNodeReport, ); - const cc = new PowerlevelCCTestNodeReport(this.applHost, { + const cc = new PowerlevelCCTestNodeReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -224,10 +224,9 @@ export type PowerlevelCCSetOptions = @useSupervision() export class PowerlevelCCSet extends PowerlevelCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | PowerlevelCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.powerlevel = this.payload[0]; @@ -251,12 +250,12 @@ export class PowerlevelCCSet extends PowerlevelCC { public powerlevel: Powerlevel; public timeout?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.powerlevel, this.timeout ?? 0x00]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "power level": getEnumMemberName(Powerlevel, this.powerlevel), }; @@ -264,7 +263,7 @@ export class PowerlevelCCSet extends PowerlevelCC { message.timeout = `${this.timeout} s`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -282,12 +281,11 @@ export type PowerlevelCCReportOptions = { @CCCommand(PowerlevelCommand.Report) export class PowerlevelCCReport extends PowerlevelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (PowerlevelCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.powerlevel = this.payload[0]; @@ -303,12 +301,12 @@ export class PowerlevelCCReport extends PowerlevelCC { public readonly powerlevel: Powerlevel; public readonly timeout?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.powerlevel, this.timeout ?? 0x00]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "power level": getEnumMemberName(Powerlevel, this.powerlevel), }; @@ -316,7 +314,7 @@ export class PowerlevelCCReport extends PowerlevelCC { message.timeout = `${this.timeout} s`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -337,12 +335,11 @@ export interface PowerlevelCCTestNodeSetOptions extends CCCommandOptions { @useSupervision() export class PowerlevelCCTestNodeSet extends PowerlevelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | PowerlevelCCTestNodeSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.testNodeId = this.payload[0]; @@ -359,15 +356,15 @@ export class PowerlevelCCTestNodeSet extends PowerlevelCC { public powerlevel: Powerlevel; public testFrameCount: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.testNodeId, this.powerlevel, 0, 0]); this.payload.writeUInt16BE(this.testFrameCount, 2); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "test node id": this.testNodeId, "power level": getEnumMemberName(Powerlevel, this.powerlevel), @@ -387,12 +384,11 @@ export interface PowerlevelCCTestNodeReportOptions { @CCCommand(PowerlevelCommand.TestNodeReport) export class PowerlevelCCTestNodeReport extends PowerlevelCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (PowerlevelCCTestNodeReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); @@ -410,7 +406,7 @@ export class PowerlevelCCTestNodeReport extends PowerlevelCC { public status: PowerlevelTestStatus; public acknowledgedFrames: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.testNodeId, this.status, @@ -419,12 +415,12 @@ export class PowerlevelCCTestNodeReport extends PowerlevelCC { 0, ]); this.payload.writeUInt16BE(this.acknowledgedFrames, 2); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "test node id": this.testNodeId, status: getEnumMemberName(PowerlevelTestStatus, this.status), diff --git a/packages/cc/src/cc/ProtectionCC.ts b/packages/cc/src/cc/ProtectionCC.ts index 4e2985238cff..12a29d5e0a1c 100644 --- a/packages/cc/src/cc/ProtectionCC.ts +++ b/packages/cc/src/cc/ProtectionCC.ts @@ -14,11 +14,7 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -35,6 +31,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -220,11 +220,11 @@ export class ProtectionCCAPI extends CCAPI { public async get() { this.assertSupportsCommand(ProtectionCommand, ProtectionCommand.Get); - const cc = new ProtectionCCGet(this.applHost, { + const cc = new ProtectionCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -240,13 +240,13 @@ export class ProtectionCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(ProtectionCommand, ProtectionCommand.Set); - const cc = new ProtectionCCSet(this.applHost, { + const cc = new ProtectionCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, local, rf, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -256,11 +256,11 @@ export class ProtectionCCAPI extends CCAPI { ProtectionCommand.SupportedGet, ); - const cc = new ProtectionCCSupportedGet(this.applHost, { + const cc = new ProtectionCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCSupportedReport >( cc, @@ -282,11 +282,11 @@ export class ProtectionCCAPI extends CCAPI { ProtectionCommand.ExclusiveControlGet, ); - const cc = new ProtectionCCExclusiveControlGet(this.applHost, { + const cc = new ProtectionCCExclusiveControlGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCExclusiveControlReport >( cc, @@ -304,12 +304,12 @@ export class ProtectionCCAPI extends CCAPI { ProtectionCommand.ExclusiveControlSet, ); - const cc = new ProtectionCCExclusiveControlSet(this.applHost, { + const cc = new ProtectionCCExclusiveControlSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, exclusiveControlNodeId: nodeId, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getTimeout(): Promise> { @@ -318,11 +318,11 @@ export class ProtectionCCAPI extends CCAPI { ProtectionCommand.TimeoutGet, ); - const cc = new ProtectionCCTimeoutGet(this.applHost, { + const cc = new ProtectionCCTimeoutGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCTimeoutReport >( cc, @@ -340,12 +340,12 @@ export class ProtectionCCAPI extends CCAPI { ProtectionCommand.TimeoutSet, ); - const cc = new ProtectionCCTimeoutSet(this.applHost, { + const cc = new ProtectionCCTimeoutSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, timeout, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -355,18 +355,20 @@ export class ProtectionCCAPI extends CCAPI { export class ProtectionCC extends CommandClass { declare ccCommand: ProtectionCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Protection, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -377,8 +379,8 @@ export class ProtectionCC extends CommandClass { let hadCriticalTimeout = false; // First find out what the device supports - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { message: "querying protection capabilities...", direction: "outbound", }); @@ -403,7 +405,7 @@ RF protection states: ${ .map((str) => `\n· ${str}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -412,34 +414,36 @@ RF protection states: ${ } } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - if (!hadCriticalTimeout) this.setInterviewComplete(applHost, true); + if (!hadCriticalTimeout) this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Protection, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const supportsExclusiveControl = !!this.getValue( - applHost, + ctx, ProtectionCCValues.supportsExclusiveControl, ); const supportsTimeout = !!this.getValue( - applHost, + ctx, ProtectionCCValues.supportsTimeout, ); // Query the current state - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying protection status...", direction: "outbound", }); @@ -451,7 +455,7 @@ local: ${getEnumMemberName(LocalProtectionState, protectionResp.local)}`; logMessage += ` rf ${getEnumMemberName(RFProtectionState, protectionResp.rf)}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -459,13 +463,13 @@ rf ${getEnumMemberName(RFProtectionState, protectionResp.rf)}`; if (supportsTimeout) { // Query the current timeout - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying protection timeout...", direction: "outbound", }); const timeout = await api.getTimeout(); if (timeout) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `received timeout: ${timeout.toString()}`, direction: "inbound", }); @@ -474,13 +478,13 @@ rf ${getEnumMemberName(RFProtectionState, protectionResp.rf)}`; if (supportsExclusiveControl) { // Query the current timeout - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying exclusive control node...", direction: "outbound", }); const nodeId = await api.getExclusiveControl(); if (nodeId != undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: (nodeId !== 0 ? `Node ${padStart(nodeId.toString(), 3, "0")}` : `no node`) + ` has exclusive control`, @@ -501,10 +505,9 @@ export interface ProtectionCCSetOptions extends CCCommandOptions { @useSupervision() export class ProtectionCCSet extends ProtectionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | ProtectionCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -520,14 +523,15 @@ export class ProtectionCCSet extends ProtectionCC { public local: LocalProtectionState; public rf?: RFProtectionState; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.local & 0b1111, (this.rf ?? RFProtectionState.Unprotected) & 0b1111, ]); + const ccVersion = getEffectiveCCVersion(ctx, this); if ( - this.version < 2 && this.host.getDeviceConfig?.( + ccVersion < 2 && ctx.getDeviceConfig?.( this.nodeId as number, )?.compat?.encodeCCsUsingTargetVersion ) { @@ -535,10 +539,10 @@ export class ProtectionCCSet extends ProtectionCC { this.payload = this.payload.subarray(0, 1); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { local: getEnumMemberName(LocalProtectionState, this.local), }; @@ -546,7 +550,7 @@ export class ProtectionCCSet extends ProtectionCC { message.rf = getEnumMemberName(RFProtectionState, this.rf); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -555,10 +559,9 @@ export class ProtectionCCSet extends ProtectionCC { @CCCommand(ProtectionCommand.Report) export class ProtectionCCReport extends ProtectionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.local = this.payload[0] & 0b1111; if (this.payload.length >= 2) { @@ -572,7 +575,7 @@ export class ProtectionCCReport extends ProtectionCC { @ccValue(ProtectionCCValues.rfProtectionState) public readonly rf?: RFProtectionState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { local: getEnumMemberName(LocalProtectionState, this.local), }; @@ -580,7 +583,7 @@ export class ProtectionCCReport extends ProtectionCC { message.rf = getEnumMemberName(RFProtectionState, this.rf); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -593,10 +596,9 @@ export class ProtectionCCGet extends ProtectionCC {} @CCCommand(ProtectionCommand.SupportedReport) export class ProtectionCCSupportedReport extends ProtectionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 5); this.supportsTimeout = !!(this.payload[0] & 0b1); this.supportsExclusiveControl = !!(this.payload[0] & 0b10); @@ -610,12 +612,12 @@ export class ProtectionCCSupportedReport extends ProtectionCC { ); } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // update metadata (partially) for the local and rf values const localStateValue = ProtectionCCValues.localProtectionState; - this.setMetadata(applHost, localStateValue, { + this.setMetadata(ctx, localStateValue, { ...localStateValue.meta, states: enumValuesToMetadataStates( LocalProtectionState, @@ -624,7 +626,7 @@ export class ProtectionCCSupportedReport extends ProtectionCC { }); const rfStateValue = ProtectionCCValues.rfProtectionState; - this.setMetadata(applHost, rfStateValue, { + this.setMetadata(ctx, rfStateValue, { ...rfStateValue.meta, states: enumValuesToMetadataStates( RFProtectionState, @@ -647,9 +649,9 @@ export class ProtectionCCSupportedReport extends ProtectionCC { @ccValue(ProtectionCCValues.supportedRFStates) public readonly supportedRFStates: RFProtectionState[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supports exclusive control": this.supportsExclusiveControl, "supports timeout": this.supportsTimeout, @@ -675,10 +677,9 @@ export class ProtectionCCSupportedGet extends ProtectionCC {} @CCCommand(ProtectionCommand.ExclusiveControlReport) export class ProtectionCCExclusiveControlReport extends ProtectionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.exclusiveControlNodeId = this.payload[0]; } @@ -686,9 +687,9 @@ export class ProtectionCCExclusiveControlReport extends ProtectionCC { @ccValue(ProtectionCCValues.exclusiveControlNodeId) public readonly exclusiveControlNodeId: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "exclusive control node id": this.exclusiveControlNodeId, }, @@ -712,12 +713,11 @@ export interface ProtectionCCExclusiveControlSetOptions @useSupervision() export class ProtectionCCExclusiveControlSet extends ProtectionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ProtectionCCExclusiveControlSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -731,14 +731,14 @@ export class ProtectionCCExclusiveControlSet extends ProtectionCC { public exclusiveControlNodeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.exclusiveControlNodeId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "exclusive control node id": this.exclusiveControlNodeId, }, @@ -749,10 +749,9 @@ export class ProtectionCCExclusiveControlSet extends ProtectionCC { @CCCommand(ProtectionCommand.TimeoutReport) export class ProtectionCCTimeoutReport extends ProtectionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.timeout = Timeout.parse(this.payload[0]); } @@ -760,9 +759,9 @@ export class ProtectionCCTimeoutReport extends ProtectionCC { @ccValue(ProtectionCCValues.timeout) public readonly timeout: Timeout; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { timeout: this.timeout.toString() }, }; } @@ -782,12 +781,11 @@ export interface ProtectionCCTimeoutSetOptions extends CCCommandOptions { @useSupervision() export class ProtectionCCTimeoutSet extends ProtectionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ProtectionCCTimeoutSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -801,14 +799,14 @@ export class ProtectionCCTimeoutSet extends ProtectionCC { public timeout: Timeout; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.timeout.serialize()]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { timeout: this.timeout.toString() }, }; } diff --git a/packages/cc/src/cc/SceneActivationCC.ts b/packages/cc/src/cc/SceneActivationCC.ts index f00677c62bac..0793bd8204ad 100644 --- a/packages/cc/src/cc/SceneActivationCC.ts +++ b/packages/cc/src/cc/SceneActivationCC.ts @@ -10,7 +10,7 @@ import { ValueMetadata, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -108,13 +108,13 @@ export class SceneActivationCCAPI extends CCAPI { SceneActivationCommand.Set, ); - const cc = new SceneActivationCCSet(this.applHost, { + const cc = new SceneActivationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sceneId, dimmingDuration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -135,12 +135,11 @@ export interface SceneActivationCCSetOptions extends CCCommandOptions { @useSupervision() export class SceneActivationCCSet extends SceneActivationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SceneActivationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.sceneId = this.payload[0]; @@ -162,21 +161,21 @@ export class SceneActivationCCSet extends SceneActivationCC { @ccValue(SceneActivationCCValues.dimmingDuration) public dimmingDuration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.sceneId, this.dimmingDuration?.serializeSet() ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "scene id": this.sceneId }; if (this.dimmingDuration != undefined) { message["dimming duration"] = this.dimmingDuration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts index 1960150f7028..d2d6640025bf 100644 --- a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts +++ b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts @@ -11,11 +11,7 @@ import { getCCName, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -33,6 +29,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -207,7 +205,7 @@ export class SceneActuatorConfigurationCCAPI extends CCAPI { // Undefined `dimmingDuration` defaults to 0 seconds to simplify the call // for actuators that don't support non-instant `dimmingDuration` // Undefined `level` uses the actuator's current value (override = 0). - const cc = new SceneActuatorConfigurationCCSet(this.applHost, { + const cc = new SceneActuatorConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sceneId, @@ -216,7 +214,7 @@ export class SceneActuatorConfigurationCCAPI extends CCAPI { level, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getActive(): Promise< @@ -232,12 +230,12 @@ export class SceneActuatorConfigurationCCAPI extends CCAPI { SceneActuatorConfigurationCommand.Get, ); - const cc = new SceneActuatorConfigurationCCGet(this.applHost, { + const cc = new SceneActuatorConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sceneId: 0, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneActuatorConfigurationCCReport >( cc, @@ -272,12 +270,12 @@ export class SceneActuatorConfigurationCCAPI extends CCAPI { ); } - const cc = new SceneActuatorConfigurationCCGet(this.applHost, { + const cc = new SceneActuatorConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, sceneId: sceneId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneActuatorConfigurationCCReport >( cc, @@ -297,10 +295,12 @@ export class SceneActuatorConfigurationCC extends CommandClass { declare ccCommand: SceneActuatorConfigurationCommand; // eslint-disable-next-line @typescript-eslint/require-await - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `${this.constructor.name}: setting metadata`, direction: "none", }); @@ -310,28 +310,28 @@ export class SceneActuatorConfigurationCC extends CommandClass { const levelValue = SceneActuatorConfigurationCCValues.level( sceneId, ); - this.ensureMetadata(applHost, levelValue); + this.ensureMetadata(ctx, levelValue); const dimmingDurationValue = SceneActuatorConfigurationCCValues .dimmingDuration(sceneId); - this.ensureMetadata(applHost, dimmingDurationValue); + this.ensureMetadata(ctx, dimmingDurationValue); } - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } // `refreshValues()` would create 255 `Get` commands to be issued to the node // Therefore, I think we should not implement it. Here is how it would be implemented // - // public async refreshValues(applHost: ZWaveApplicationHost): Promise { - // const node = this.getNode(applHost)!; - // const endpoint = this.getEndpoint(applHost)!; + // public async refreshValues(ctx: RefreshValuesContext): Promise { + // const node = this.getNode(ctx)!; + // const endpoint = this.getEndpoint(ctx)!; // const api = endpoint.commandClasses[ // "Scene Actuator Configuration" // ].withOptions({ // priority: MessagePriority.NodeQuery, // }); - // this.applHost.controllerLog.logNode(node.id, { + // ctx.logNode(node.id, { // message: "querying all scene actuator configs...", // direction: "outbound", // }); @@ -356,12 +356,11 @@ export class SceneActuatorConfigurationCCSet extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SceneActuatorConfigurationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -385,17 +384,17 @@ export class SceneActuatorConfigurationCCSet public dimmingDuration: Duration; public level?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.sceneId, this.dimmingDuration.serializeSet(), this.level != undefined ? 0b1000_0000 : 0, this.level ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { sceneId: this.sceneId, dimmingDuration: this.dimmingDuration.toString(), @@ -405,7 +404,7 @@ export class SceneActuatorConfigurationCCSet } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -416,10 +415,9 @@ export class SceneActuatorConfigurationCCReport extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 3); this.sceneId = this.payload[0]; @@ -434,8 +432,8 @@ export class SceneActuatorConfigurationCCReport public readonly level?: number; public readonly dimmingDuration?: Duration; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Do not persist values for an inactive scene if ( @@ -449,19 +447,19 @@ export class SceneActuatorConfigurationCCReport const levelValue = SceneActuatorConfigurationCCValues.level( this.sceneId, ); - this.ensureMetadata(applHost, levelValue); + this.ensureMetadata(ctx, levelValue); const dimmingDurationValue = SceneActuatorConfigurationCCValues .dimmingDuration(this.sceneId); - this.ensureMetadata(applHost, dimmingDurationValue); + this.ensureMetadata(ctx, dimmingDurationValue); - this.setValue(applHost, levelValue, this.level); - this.setValue(applHost, dimmingDurationValue, this.dimmingDuration); + this.setValue(ctx, levelValue, this.level); + this.setValue(ctx, dimmingDurationValue, this.dimmingDuration); return true; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { sceneId: this.sceneId, }; @@ -473,7 +471,7 @@ export class SceneActuatorConfigurationCCReport } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -504,12 +502,11 @@ export class SceneActuatorConfigurationCCGet extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SceneActuatorConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -523,14 +520,14 @@ export class SceneActuatorConfigurationCCGet public sceneId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sceneId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "scene id": this.sceneId }, }; } diff --git a/packages/cc/src/cc/SceneControllerConfigurationCC.ts b/packages/cc/src/cc/SceneControllerConfigurationCC.ts index 077051654a73..f028bf3dfa4c 100644 --- a/packages/cc/src/cc/SceneControllerConfigurationCC.ts +++ b/packages/cc/src/cc/SceneControllerConfigurationCC.ts @@ -1,7 +1,7 @@ import { CommandClasses, Duration, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -13,9 +13,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -34,6 +34,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -245,7 +248,7 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { if (!this.endpoint.virtual) { const groupCount = SceneControllerConfigurationCC .getGroupCountCached( - this.applHost, + this.host, this.endpoint, ); @@ -265,7 +268,7 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { ); } - const cc = new SceneControllerConfigurationCCSet(this.applHost, { + const cc = new SceneControllerConfigurationCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, @@ -273,7 +276,7 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { dimmingDuration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getLastActivated(): Promise< @@ -289,12 +292,12 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { SceneControllerConfigurationCommand.Get, ); - const cc = new SceneControllerConfigurationCCGet(this.applHost, { + const cc = new SceneControllerConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId: 0, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneControllerConfigurationCCReport >( cc, @@ -337,12 +340,12 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { ); } - const cc = new SceneControllerConfigurationCCGet(this.applHost, { + const cc = new SceneControllerConfigurationCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneControllerConfigurationCCReport >( cc, @@ -374,22 +377,24 @@ export class SceneControllerConfigurationCC extends CommandClass { } // eslint-disable-next-line @typescript-eslint/require-await - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); const groupCount = SceneControllerConfigurationCC.getGroupCountCached( - applHost, + ctx, endpoint, ); if (groupCount === 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `skipping Scene Controller Configuration interview because Association group count is unknown`, @@ -405,39 +410,41 @@ export class SceneControllerConfigurationCC extends CommandClass { const sceneIdValue = SceneControllerConfigurationCCValues.sceneId( groupId, ); - this.ensureMetadata(applHost, sceneIdValue); + this.ensureMetadata(ctx, sceneIdValue); const dimmingDurationValue = SceneControllerConfigurationCCValues .dimmingDuration(groupId); - this.ensureMetadata(applHost, dimmingDurationValue); + this.ensureMetadata(ctx, dimmingDurationValue); } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Scene Controller Configuration"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const groupCount = SceneControllerConfigurationCC.getGroupCountCached( - applHost, + ctx, endpoint, ); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying all scene controller configurations...", direction: "outbound", }); for (let groupId = 1; groupId <= groupCount; groupId++) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying scene configuration for group #${groupId}...`, @@ -449,7 +456,7 @@ export class SceneControllerConfigurationCC extends CommandClass { `received scene configuration for group #${groupId}: scene ID: ${group.sceneId} dimming duration: ${group.dimmingDuration.toString()}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -464,15 +471,13 @@ dimming duration: ${group.dimmingDuration.toString()}`; * or the AssociationCC. */ public static getGroupCountCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & GetDeviceConfig, + endpoint: EndpointId, ): number { - return ( - applHost.getDeviceConfig?.(endpoint.nodeId)?.compat - ?.forceSceneControllerGroupCount - ?? AssociationCC.getGroupCountCached(applHost, endpoint) - ?? 0 - ); + return ctx.getDeviceConfig?.(endpoint.nodeId)?.compat + ?.forceSceneControllerGroupCount + ?? AssociationCC.getGroupCountCached(ctx, endpoint) + ?? 0; } } @@ -491,12 +496,11 @@ export class SceneControllerConfigurationCCSet extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SceneControllerConfigurationCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -516,18 +520,18 @@ export class SceneControllerConfigurationCCSet public sceneId: number; public dimmingDuration: Duration; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.groupId, this.sceneId, this.dimmingDuration.serializeSet(), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "scene id": this.sceneId, @@ -542,10 +546,9 @@ export class SceneControllerConfigurationCCReport extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 3); this.groupId = this.payload[0]; this.sceneId = this.payload[1]; @@ -557,8 +560,8 @@ export class SceneControllerConfigurationCCReport public readonly sceneId: number; public readonly dimmingDuration: Duration; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // If groupId = 0, values are meaningless if (this.groupId === 0) return false; @@ -566,20 +569,20 @@ export class SceneControllerConfigurationCCReport const sceneIdValue = SceneControllerConfigurationCCValues.sceneId( this.groupId, ); - this.ensureMetadata(applHost, sceneIdValue); + this.ensureMetadata(ctx, sceneIdValue); const dimmingDurationValue = SceneControllerConfigurationCCValues .dimmingDuration(this.groupId); - this.ensureMetadata(applHost, dimmingDurationValue); + this.ensureMetadata(ctx, dimmingDurationValue); - this.setValue(applHost, sceneIdValue, this.sceneId); - this.setValue(applHost, dimmingDurationValue, this.dimmingDuration); + this.setValue(ctx, sceneIdValue, this.sceneId); + this.setValue(ctx, dimmingDurationValue, this.dimmingDuration); return true; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId, "scene id": this.sceneId, @@ -614,12 +617,11 @@ export class SceneControllerConfigurationCCGet extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SceneControllerConfigurationCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -633,14 +635,14 @@ export class SceneControllerConfigurationCCGet public groupId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.groupId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "group id": this.groupId }, }; } diff --git a/packages/cc/src/cc/ScheduleEntryLockCC.ts b/packages/cc/src/cc/ScheduleEntryLockCC.ts index b3c047476c5d..f8e0782526bd 100644 --- a/packages/cc/src/cc/ScheduleEntryLockCC.ts +++ b/packages/cc/src/cc/ScheduleEntryLockCC.ts @@ -1,6 +1,5 @@ import { CommandClasses, - type IZWaveEndpoint, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, @@ -13,12 +12,8 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core"; -import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host"; +import { type EndpointId, type MaybeNotKnown } from "@zwave-js/core/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host"; import { type AllOrNone, formatDate, @@ -32,6 +27,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -116,7 +113,7 @@ function toPropertyKey( /** Caches information about a schedule */ function persistSchedule( this: ScheduleEntryLockCC, - applHost: ZWaveApplicationHost, + ctx: GetValueDB, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number, @@ -134,20 +131,20 @@ function persistSchedule( ); if (schedule != undefined) { - this.setValue(applHost, scheduleValue, schedule); + this.setValue(ctx, scheduleValue, schedule); } else { - this.removeValue(applHost, scheduleValue); + this.removeValue(ctx, scheduleValue); } } /** Updates the schedule kind assumed to be active for user in the cache */ function setUserCodeScheduleKindCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number, scheduleKind: ScheduleEntryLockScheduleKind, ): void { - applHost + ctx .getValueDB(endpoint.nodeId) .setValue( ScheduleEntryLockCCValues.scheduleKind(userId).endpoint( @@ -159,13 +156,13 @@ function setUserCodeScheduleKindCached( /** Updates whether scheduling is active for one or all user(s) in the cache */ function setUserCodeScheduleEnabledCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number | undefined, enabled: boolean, ): void { const setEnabled = (userId: number) => { - applHost + ctx .getValueDB(endpoint.nodeId) .setValue( ScheduleEntryLockCCValues.userEnabled(userId).endpoint( @@ -177,7 +174,7 @@ function setUserCodeScheduleEnabledCached( if (userId == undefined) { // Enable/disable all users - const numUsers = UserCodeCC.getSupportedUsersCached(applHost, endpoint) + const numUsers = UserCodeCC.getSupportedUsersCached(ctx, endpoint) ?? 0; for (let userId = 1; userId <= numUsers; userId++) { @@ -231,33 +228,33 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.EnableSet, ); - const cc = new ScheduleEntryLockCCEnableSet(this.applHost, { + const cc = new ScheduleEntryLockCCEnableSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userId, enabled, }); - result = await this.applHost.sendCommand(cc, this.commandOptions); + result = await this.host.sendCommand(cc, this.commandOptions); } else { this.assertSupportsCommand( ScheduleEntryLockCommand, ScheduleEntryLockCommand.EnableAllSet, ); - const cc = new ScheduleEntryLockCCEnableAllSet(this.applHost, { + const cc = new ScheduleEntryLockCCEnableAllSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, enabled, }); - result = await this.applHost.sendCommand(cc, this.commandOptions); + result = await this.host.sendCommand(cc, this.commandOptions); } if (this.isSinglecast() && isUnsupervisedOrSucceeded(result)) { // Remember the new state in the cache setUserCodeScheduleEnabledCached( - this.applHost, + this.host, this.endpoint, userId, enabled, @@ -274,12 +271,12 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.SupportedGet, ); - const cc = new ScheduleEntryLockCCSupportedGet(this.applHost, { + const cc = new ScheduleEntryLockCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCSupportedReport >( cc, @@ -307,7 +304,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC.getNumWeekDaySlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -332,7 +329,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { } } - const cc = new ScheduleEntryLockCCWeekDayScheduleSet(this.applHost, { + const cc = new ScheduleEntryLockCCWeekDayScheduleSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...slot, @@ -346,20 +343,20 @@ export class ScheduleEntryLockCCAPI extends CCAPI { }), }); - const result = await this.applHost.sendCommand(cc, this.commandOptions); + const result = await this.host.sendCommand(cc, this.commandOptions); if (this.isSinglecast() && isUnsupervisedOrSucceeded(result)) { // Editing (but not erasing) a schedule will enable scheduling for that user // and switch it to the current scheduling kind if (!!schedule) { setUserCodeScheduleEnabledCached( - this.applHost, + this.host, this.endpoint, slot.userId, true, ); setUserCodeScheduleKindCached( - this.applHost, + this.host, this.endpoint, slot.userId, ScheduleEntryLockScheduleKind.WeekDay, @@ -369,7 +366,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { // And cache the schedule persistSchedule.call( cc, - this.applHost, + this.host, ScheduleEntryLockScheduleKind.WeekDay, slot.userId, slot.slotId, @@ -389,12 +386,12 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.WeekDayScheduleSet, ); - const cc = new ScheduleEntryLockCCWeekDayScheduleGet(this.applHost, { + const cc = new ScheduleEntryLockCCWeekDayScheduleGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...slot, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCWeekDayScheduleReport >( cc, @@ -424,7 +421,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC.getNumYearDaySlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -459,7 +456,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { } } - const cc = new ScheduleEntryLockCCYearDayScheduleSet(this.applHost, { + const cc = new ScheduleEntryLockCCYearDayScheduleSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...slot, @@ -473,20 +470,20 @@ export class ScheduleEntryLockCCAPI extends CCAPI { }), }); - const result = await this.applHost.sendCommand(cc, this.commandOptions); + const result = await this.host.sendCommand(cc, this.commandOptions); if (this.isSinglecast() && isUnsupervisedOrSucceeded(result)) { // Editing (but not erasing) a schedule will enable scheduling for that user // and switch it to the current scheduling kind if (!!schedule) { setUserCodeScheduleEnabledCached( - this.applHost, + this.host, this.endpoint, slot.userId, true, ); setUserCodeScheduleKindCached( - this.applHost, + this.host, this.endpoint, slot.userId, ScheduleEntryLockScheduleKind.YearDay, @@ -496,7 +493,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { // And cache the schedule persistSchedule.call( cc, - this.applHost, + this.host, ScheduleEntryLockScheduleKind.YearDay, slot.userId, slot.slotId, @@ -516,12 +513,12 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.YearDayScheduleSet, ); - const cc = new ScheduleEntryLockCCYearDayScheduleGet(this.applHost, { + const cc = new ScheduleEntryLockCCYearDayScheduleGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...slot, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCYearDayScheduleReport >( cc, @@ -557,7 +554,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC .getNumDailyRepeatingSlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -569,37 +566,34 @@ export class ScheduleEntryLockCCAPI extends CCAPI { } } - const cc = new ScheduleEntryLockCCDailyRepeatingScheduleSet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - ...slot, - ...(schedule - ? { - action: ScheduleEntryLockSetAction.Set, - ...schedule, - } - : { - action: ScheduleEntryLockSetAction.Erase, - }), - }, - ); + const cc = new ScheduleEntryLockCCDailyRepeatingScheduleSet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + ...slot, + ...(schedule + ? { + action: ScheduleEntryLockSetAction.Set, + ...schedule, + } + : { + action: ScheduleEntryLockSetAction.Erase, + }), + }); - const result = await this.applHost.sendCommand(cc, this.commandOptions); + const result = await this.host.sendCommand(cc, this.commandOptions); if (this.isSinglecast() && isUnsupervisedOrSucceeded(result)) { // Editing (but not erasing) a schedule will enable scheduling for that user // and switch it to the current scheduling kind if (!!schedule) { setUserCodeScheduleEnabledCached( - this.applHost, + this.host, this.endpoint, slot.userId, true, ); setUserCodeScheduleKindCached( - this.applHost, + this.host, this.endpoint, slot.userId, ScheduleEntryLockScheduleKind.DailyRepeating, @@ -609,7 +603,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { // And cache the schedule persistSchedule.call( cc, - this.applHost, + this.host, ScheduleEntryLockScheduleKind.DailyRepeating, slot.userId, slot.slotId, @@ -629,15 +623,12 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.DailyRepeatingScheduleSet, ); - const cc = new ScheduleEntryLockCCDailyRepeatingScheduleGet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - ...slot, - }, - ); - const result = await this.applHost.sendCommand< + const cc = new ScheduleEntryLockCCDailyRepeatingScheduleGet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + ...slot, + }); + const result = await this.host.sendCommand< ScheduleEntryLockCCDailyRepeatingScheduleReport >( cc, @@ -661,11 +652,11 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.TimeOffsetGet, ); - const cc = new ScheduleEntryLockCCTimeOffsetGet(this.applHost, { + const cc = new ScheduleEntryLockCCTimeOffsetGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCTimeOffsetReport >( cc, @@ -686,13 +677,13 @@ export class ScheduleEntryLockCCAPI extends CCAPI { ScheduleEntryLockCommand.TimeOffsetSet, ); - const cc = new ScheduleEntryLockCCTimeOffsetSet(this.applHost, { + const cc = new ScheduleEntryLockCCTimeOffsetSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...timezone, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -702,24 +693,26 @@ export class ScheduleEntryLockCCAPI extends CCAPI { export class ScheduleEntryLockCC extends CommandClass { declare ccCommand: ScheduleEntryLockCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Schedule Entry Lock"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported number of schedule slots...", direction: "outbound", @@ -733,7 +726,7 @@ day of year: ${slotsResp.numYearDaySlots}`; logMessage += ` daily repeating: ${slotsResp.numDailyRepeatingSlots}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -746,7 +739,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; && (!endpoint.supportsCC(CommandClasses.Time) || endpoint.getCCVersion(CommandClasses.Time) < 2) ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "setting timezone information...", direction: "outbound", @@ -757,7 +750,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } /** @@ -765,18 +758,16 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * This only works AFTER the interview process */ public static getNumWeekDaySlotsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - ScheduleEntryLockCCValues.numWeekDaySlots.endpoint( - endpoint.index, - ), - ) || 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + ScheduleEntryLockCCValues.numWeekDaySlots.endpoint( + endpoint.index, + ), + ) || 0; } /** @@ -784,18 +775,16 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * This only works AFTER the interview process */ public static getNumYearDaySlotsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - ScheduleEntryLockCCValues.numYearDaySlots.endpoint( - endpoint.index, - ), - ) || 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + ScheduleEntryLockCCValues.numYearDaySlots.endpoint( + endpoint.index, + ), + ) || 0; } /** @@ -803,18 +792,16 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * This only works AFTER the interview process */ public static getNumDailyRepeatingSlotsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): number { - return ( - applHost - .getValueDB(endpoint.nodeId) - .getValue( - ScheduleEntryLockCCValues.numDailyRepeatingSlots.endpoint( - endpoint.index, - ), - ) || 0 - ); + return ctx + .getValueDB(endpoint.nodeId) + .getValue( + ScheduleEntryLockCCValues.numDailyRepeatingSlots.endpoint( + endpoint.index, + ), + ) || 0; } /** @@ -826,11 +813,11 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * only the desired ones. */ public static getUserCodeScheduleEnabledCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number, ): boolean { - return !!applHost + return !!ctx .getValueDB(endpoint.nodeId) .getValue( ScheduleEntryLockCCValues.userEnabled(userId).endpoint( @@ -848,11 +835,11 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * which will automatically switch the user to that scheduling kind. */ public static getUserCodeScheduleKindCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( ScheduleEntryLockCCValues.scheduleKind(userId).endpoint( @@ -862,24 +849,24 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; } public static getScheduleCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, scheduleKind: ScheduleEntryLockScheduleKind.WeekDay, userId: number, slotId: number, ): MaybeNotKnown; public static getScheduleCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, scheduleKind: ScheduleEntryLockScheduleKind.YearDay, userId: number, slotId: number, ): MaybeNotKnown; public static getScheduleCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, scheduleKind: ScheduleEntryLockScheduleKind.DailyRepeating, userId: number, slotId: number, @@ -887,8 +874,8 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; // Catch-all overload for applications which haven't narrowed `scheduleKind` public static getScheduleCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number, @@ -908,8 +895,8 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; * This only works AFTER the interview process. */ public static getScheduleCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number, @@ -919,7 +906,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; | ScheduleEntryLockDailyRepeatingSchedule | false > { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( ScheduleEntryLockCCValues.schedule( @@ -941,12 +928,11 @@ export interface ScheduleEntryLockCCEnableSetOptions extends CCCommandOptions { @useSupervision() export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCEnableSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -960,14 +946,14 @@ export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { public userId: number; public enabled: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId, this.enabled ? 0x01 : 0x00]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user ID": this.userId, action: this.enabled ? "enable" : "disable", @@ -987,12 +973,11 @@ export interface ScheduleEntryLockCCEnableAllSetOptions @useSupervision() export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCEnableAllSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.enabled = this.payload[0] === 0x01; @@ -1003,14 +988,14 @@ export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { public enabled: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.enabled ? 0x01 : 0x00]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { action: this.enabled ? "enable all" : "disable all", }, @@ -1030,12 +1015,11 @@ export interface ScheduleEntryLockCCSupportedReportOptions @CCCommand(ScheduleEntryLockCommand.SupportedReport) export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.numWeekDaySlots = this.payload[0]; @@ -1057,16 +1041,16 @@ export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { @ccValue(ScheduleEntryLockCCValues.numDailyRepeatingSlots) public numDailyRepeatingSlots: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.numWeekDaySlots, this.numYearDaySlots, this.numDailyRepeatingSlots ?? 0, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "no. of weekday schedule slots": this.numWeekDaySlots, "no. of day-of-year schedule slots": this.numYearDaySlots, @@ -1076,7 +1060,7 @@ export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { this.numDailyRepeatingSlots; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1103,12 +1087,11 @@ export type ScheduleEntryLockCCWeekDayScheduleSetOptions = @useSupervision() export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.action = this.payload[0]; @@ -1151,7 +1134,7 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { public stopHour?: number; public stopMinute?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.action, this.userId, @@ -1165,10 +1148,10 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { this.stopHour ?? 0xff, this.stopMinute ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (this.action === ScheduleEntryLockSetAction.Erase) { message = { @@ -1196,7 +1179,7 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1213,12 +1196,11 @@ export class ScheduleEntryLockCCWeekDayScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -1259,12 +1241,12 @@ export class ScheduleEntryLockCCWeekDayScheduleReport public stopHour?: number; public stopMinute?: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; persistSchedule.call( this, - applHost, + ctx, ScheduleEntryLockScheduleKind.WeekDay, this.userId, this.slotId, @@ -1282,7 +1264,7 @@ export class ScheduleEntryLockCCWeekDayScheduleReport return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.userId, this.slotId, @@ -1292,10 +1274,10 @@ export class ScheduleEntryLockCCWeekDayScheduleReport this.stopHour ?? 0xff, this.stopMinute ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (this.weekday == undefined) { message = { @@ -1322,7 +1304,7 @@ export class ScheduleEntryLockCCWeekDayScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1337,12 +1319,11 @@ export type ScheduleEntryLockCCWeekDayScheduleGetOptions = @expectedCCResponse(ScheduleEntryLockCCWeekDayScheduleReport) export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -1356,14 +1337,14 @@ export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { public userId: number; public slotId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId, this.slotId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user ID": this.userId, "slot #": this.slotId, @@ -1389,12 +1370,11 @@ export type ScheduleEntryLockCCYearDayScheduleSetOptions = @useSupervision() export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.action = this.payload[0]; @@ -1452,7 +1432,7 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { public stopHour?: number; public stopMinute?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.action, this.userId, @@ -1471,10 +1451,10 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { this.stopHour ?? 0xff, this.stopMinute ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (this.action === ScheduleEntryLockSetAction.Erase) { message = { @@ -1504,7 +1484,7 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1521,12 +1501,11 @@ export class ScheduleEntryLockCCYearDayScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -1592,12 +1571,12 @@ export class ScheduleEntryLockCCYearDayScheduleReport public stopHour?: number; public stopMinute?: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; persistSchedule.call( this, - applHost, + ctx, ScheduleEntryLockScheduleKind.YearDay, this.userId, this.slotId, @@ -1620,7 +1599,7 @@ export class ScheduleEntryLockCCYearDayScheduleReport return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.userId, this.slotId, @@ -1635,10 +1614,10 @@ export class ScheduleEntryLockCCYearDayScheduleReport this.stopHour ?? 0xff, this.stopMinute ?? 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (this.startYear !== undefined) { message = { @@ -1668,7 +1647,7 @@ export class ScheduleEntryLockCCYearDayScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1683,12 +1662,11 @@ export type ScheduleEntryLockCCYearDayScheduleGetOptions = @expectedCCResponse(ScheduleEntryLockCCYearDayScheduleReport) export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -1702,14 +1680,14 @@ export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { public userId: number; public slotId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId, this.slotId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user ID": this.userId, "slot #": this.slotId, @@ -1730,12 +1708,11 @@ export interface ScheduleEntryLockCCTimeOffsetSetOptions @useSupervision() export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCTimeOffsetSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const { standardOffset, dstOffset } = parseTimezone(this.payload); this.standardOffset = standardOffset; @@ -1749,17 +1726,17 @@ export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { public standardOffset: number; public dstOffset: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, }); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "standard time offset": `${this.standardOffset} minutes`, "DST offset": `${this.dstOffset} minutes`, @@ -1779,12 +1756,11 @@ export interface ScheduleEntryLockCCTimeOffsetReportOptions @CCCommand(ScheduleEntryLockCommand.TimeOffsetReport) export class ScheduleEntryLockCCTimeOffsetReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCTimeOffsetReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const { standardOffset, dstOffset } = parseTimezone(this.payload); this.standardOffset = standardOffset; @@ -1798,17 +1774,17 @@ export class ScheduleEntryLockCCTimeOffsetReport extends ScheduleEntryLockCC { public standardOffset: number; public dstOffset: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeTimezone({ standardOffset: this.standardOffset, dstOffset: this.dstOffset, }); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "standard time offset": `${this.standardOffset} minutes`, "DST offset": `${this.dstOffset} minutes`, @@ -1840,12 +1816,11 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCDailyRepeatingScheduleSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.action = this.payload[0]; @@ -1891,7 +1866,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet public durationHour?: number; public durationMinute?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.action, this.userId, this.slotId]); if (this.action === ScheduleEntryLockSetAction.Set) { this.payload = Buffer.concat([ @@ -1913,10 +1888,10 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet this.payload = Buffer.concat([this.payload, Buffer.alloc(5, 0xff)]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (this.action === ScheduleEntryLockSetAction.Erase) { message = { @@ -1943,7 +1918,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1959,7 +1934,6 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ( @@ -1967,7 +1941,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport & ScheduleEntryLockCCDailyRepeatingScheduleReportOptions ), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -2003,12 +1977,12 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport public durationHour?: number; public durationMinute?: number; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; persistSchedule.call( this, - applHost, + ctx, ScheduleEntryLockScheduleKind.DailyRepeating, this.userId, this.slotId, @@ -2026,7 +2000,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId, this.slotId]); if (this.weekdays) { this.payload = Buffer.concat([ @@ -2048,10 +2022,10 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport this.payload = Buffer.concat([this.payload, Buffer.alloc(5, 0)]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { let message: MessageRecord; if (!this.weekdays) { message = { @@ -2078,7 +2052,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2095,12 +2069,11 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ScheduleEntryLockCCDailyRepeatingScheduleGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -2114,14 +2087,14 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleGet public userId: number; public slotId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId, this.slotId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user ID": this.userId, "slot #": this.slotId, diff --git a/packages/cc/src/cc/Security2CC.ts b/packages/cc/src/cc/Security2CC.ts index f8b460c4097f..f550ff7f87b0 100644 --- a/packages/cc/src/cc/Security2CC.ts +++ b/packages/cc/src/cc/Security2CC.ts @@ -33,12 +33,13 @@ import { type MaybeNotKnown, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, + type SecurityManagers, encodeCCList, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { wait } from "alcalzone-shared/async"; @@ -49,7 +50,7 @@ import { type CCResponseRole, CommandClass, type CommandClassDeserializationOptions, - type CommandClassOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -119,57 +120,41 @@ function getAuthenticationData( unencryptedPayload.copy(ret, offset, 0); return ret; } - function getSecurityManager( - host: ZWaveHost, + ownNodeId: number, + securityManagers: SecurityManagers, destination: MulticastDestination | number, ): SecurityManager2 | undefined { - const longRange = isLongRangeNodeId(host.ownNodeId) + const longRange = isLongRangeNodeId(ownNodeId) || isLongRangeNodeId( isArray(destination) ? destination[0] : destination, ); return longRange - ? host.securityManagerLR - : host.securityManager2; + ? securityManagers.securityManagerLR + : securityManagers.securityManager2; } /** Validates that a sequence number is not a duplicate and updates the SPAN table if it is accepted. Returns the previous sequence number if there is one. */ function validateSequenceNumber( this: Security2CC, + securityManager: SecurityManager2, sequenceNumber: number, ): number | undefined { - const securityManager = getSecurityManager(this.host, this.nodeId); - validatePayload.withReason( `Duplicate command (sequence number ${sequenceNumber})`, )( - !securityManager!.isDuplicateSinglecast( + !securityManager.isDuplicateSinglecast( this.nodeId as number, sequenceNumber, ), ); // Not a duplicate, store it - return securityManager!.storeSequenceNumber( + return securityManager.storeSequenceNumber( this.nodeId as number, sequenceNumber, ); } -function assertSecurity(this: Security2CC, options: CommandClassOptions): void { - const verb = gotDeserializationOptions(options) ? "decoded" : "sent"; - if (!this.host.ownNodeId) { - throw new ZWaveError( - `Secure commands (S2) can only be ${verb} when the controller's node id is known!`, - ZWaveErrorCodes.Driver_NotReady, - ); - } else if (!getSecurityManager(this.host, this.nodeId)) { - throw new ZWaveError( - `Secure commands (S2) can only be ${verb} when the network keys are configured!`, - ZWaveErrorCodes.Driver_NoSecurity, - ); - } -} - const MAX_DECRYPT_ATTEMPTS_SINGLECAST = 5; const MAX_DECRYPT_ATTEMPTS_MULTICAST = 5; const MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP = 1; @@ -207,7 +192,8 @@ export class Security2CCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); const securityManager = getSecurityManager( - this.applHost, + this.host.ownNodeId, + this.host, this.endpoint.nodeId, ); @@ -222,16 +208,18 @@ export class Security2CCAPI extends CCAPI { this.endpoint.nodeId, ); - const cc = new Security2CCNonceReport(this.applHost, { + const cc = new Security2CCNonceReport({ nodeId: this.endpoint.nodeId, + ownNodeId: this.host.ownNodeId, endpoint: this.endpoint.index, + securityManagers: this.host, SOS: true, MOS: false, receiverEI, }); try { - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Seems we need these options or some nodes won't accept the nonce transmitOptions: TransmitOptions.ACK @@ -268,15 +256,17 @@ export class Security2CCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); - const cc = new Security2CCNonceReport(this.applHost, { + const cc = new Security2CCNonceReport({ nodeId: this.endpoint.nodeId, + ownNodeId: this.host.ownNodeId, endpoint: this.endpoint.index, + securityManagers: this.host, SOS: false, MOS: true, }); try { - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Seems we need these options or some nodes won't accept the nonce transmitOptions: TransmitOptions.ACK @@ -311,9 +301,11 @@ export class Security2CCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); - const cc = new Security2CCMessageEncapsulation(this.applHost, { + const cc = new Security2CCMessageEncapsulation({ nodeId: this.endpoint.nodeId, + ownNodeId: this.host.ownNodeId, endpoint: this.endpoint.index, + securityManagers: this.host, extensions: [ new MPANExtension({ groupId, @@ -323,7 +315,7 @@ export class Security2CCAPI extends CCAPI { }); try { - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Seems we need these options or some nodes won't accept the nonce transmitOptions: TransmitOptions.ACK @@ -361,22 +353,31 @@ export class Security2CCAPI extends CCAPI { Security2Command.CommandsSupportedGet, ); - let cc: CommandClass = new Security2CCCommandsSupportedGet( - this.applHost, - { - nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, - }, - ); + let cc: CommandClass = new Security2CCCommandsSupportedGet({ + nodeId: this.endpoint.nodeId, + endpoint: this.endpoint.index, + }); // Security2CCCommandsSupportedGet is special because we cannot reply on the applHost to do the automatic // encapsulation because it would use a different security class. Therefore the entire possible stack // of encapsulation needs to be done here if (MultiChannelCC.requiresEncapsulation(cc)) { - cc = MultiChannelCC.encapsulate(this.applHost, cc); + const multiChannelCCVersion = this.host.getSupportedCCVersion( + CommandClasses["Multi Channel"], + this.endpoint.nodeId as number, + ); + + cc = multiChannelCCVersion === 1 + ? MultiChannelCC.encapsulateV1(cc) + : MultiChannelCC.encapsulate(cc); } - cc = Security2CC.encapsulate(this.applHost, cc, { securityClass }); + cc = Security2CC.encapsulate( + cc, + this.host.ownNodeId, + this.host, + { securityClass }, + ); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< Security2CCCommandsSupportedReport >( cc, @@ -396,23 +397,23 @@ export class Security2CCAPI extends CCAPI { Security2Command.CommandsSupportedReport, ); - const cc = new Security2CCCommandsSupportedReport(this.applHost, { + const cc = new Security2CCCommandsSupportedReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, supportedCCs, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public async getKeyExchangeParameters() { this.assertSupportsCommand(Security2Command, Security2Command.KEXGet); - const cc = new Security2CCKEXGet(this.applHost, { + const cc = new Security2CCKEXGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -437,13 +438,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.KEXReport, ); - const cc = new Security2CCKEXReport(this.applHost, { + const cc = new Security2CCKEXReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...params, echo: false, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** Grants the joining node the given keys */ @@ -452,13 +453,13 @@ export class Security2CCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(Security2Command, Security2Command.KEXSet); - const cc = new Security2CCKEXSet(this.applHost, { + const cc = new Security2CCKEXSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...params, echo: false, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** Confirms the keys that were requested by a node */ @@ -470,13 +471,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.KEXReport, ); - const cc = new Security2CCKEXReport(this.applHost, { + const cc = new Security2CCKEXReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...params, echo: true, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** Confirms the keys that were granted by the including node */ @@ -488,25 +489,25 @@ export class Security2CCAPI extends CCAPI { Security2Command.KEXSet, ); - const cc = new Security2CCKEXSet(this.applHost, { + const cc = new Security2CCKEXSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...params, echo: true, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** Notifies the other node that the ongoing key exchange was aborted */ public async abortKeyExchange(failType: KEXFailType): Promise { this.assertSupportsCommand(Security2Command, Security2Command.KEXFail); - const cc = new Security2CCKEXFail(this.applHost, { + const cc = new Security2CCKEXFail({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, failType, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async sendPublicKey( @@ -518,13 +519,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.PublicKeyReport, ); - const cc = new Security2CCPublicKeyReport(this.applHost, { + const cc = new Security2CCPublicKeyReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, includingNode, publicKey, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async requestNetworkKey( @@ -535,12 +536,12 @@ export class Security2CCAPI extends CCAPI { Security2Command.NetworkKeyGet, ); - const cc = new Security2CCNetworkKeyGet(this.applHost, { + const cc = new Security2CCNetworkKeyGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, requestedKey: securityClass, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async sendNetworkKey( @@ -552,13 +553,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.NetworkKeyReport, ); - const cc = new Security2CCNetworkKeyReport(this.applHost, { + const cc = new Security2CCNetworkKeyReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, grantedKey: securityClass, networkKey, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async verifyNetworkKey(): Promise { @@ -567,11 +568,11 @@ export class Security2CCAPI extends CCAPI { Security2Command.NetworkKeyVerify, ); - const cc = new Security2CCNetworkKeyVerify(this.applHost, { + const cc = new Security2CCNetworkKeyVerify({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async confirmKeyVerification(): Promise { @@ -580,13 +581,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.TransferEnd, ); - const cc = new Security2CCTransferEnd(this.applHost, { + const cc = new Security2CCTransferEnd({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, keyVerified: true, keyRequestComplete: false, }); - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Don't wait for an ACK from the node transmitOptions: TransmitOptions.DEFAULT & ~TransmitOptions.ACK, @@ -599,13 +600,13 @@ export class Security2CCAPI extends CCAPI { Security2Command.TransferEnd, ); - const cc = new Security2CCTransferEnd(this.applHost, { + const cc = new Security2CCTransferEnd({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, keyVerified: false, keyRequestComplete: true, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -614,18 +615,68 @@ export class Security2CCAPI extends CCAPI { export class Security2CC extends CommandClass { declare ccCommand: Security2Command; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + protected assertSecurity( + options: + | (CCCommandOptions & { + ownNodeId: number; + securityManagers: SecurityManagers; + }) + | CommandClassDeserializationOptions, + ): SecurityManager2 { + const verb = gotDeserializationOptions(options) ? "decoded" : "sent"; + const ownNodeId = gotDeserializationOptions(options) + ? options.context.ownNodeId + : options.ownNodeId; + if (!ownNodeId) { + throw new ZWaveError( + `Secure commands (S2) can only be ${verb} when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + let ret: SecurityManager2 | undefined; + if (gotDeserializationOptions(options)) { + ret = getSecurityManager( + ownNodeId, + options.context, + this.nodeId, + )!; + } else { + ret = getSecurityManager( + ownNodeId, + options.securityManagers, + this.nodeId, + )!; + } + + if (!ret) { + throw new ZWaveError( + `Secure commands (S2) can only be ${verb} when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } + + return ret; + } + + public async interview( + ctx: InterviewContext, + ): Promise { + const securityManager = getSecurityManager( + ctx.ownNodeId, + ctx, + this.nodeId, + ); + + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Security 2"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - const securityManager = getSecurityManager(applHost, node.id); - // Only on the highest security class the response includes the supported commands const secClass = node.getHighestSecurityClass(); let hasReceivedSecureCommands = false; @@ -644,7 +695,7 @@ export class Security2CC extends CommandClass { ]; } else { // For endpoint interviews, the security class MUST be known - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Cannot query securely supported commands for endpoint because the node's security class isn't known...`, @@ -668,7 +719,7 @@ export class Security2CC extends CommandClass { if ( !securityManager?.hasKeysForSecurityClass(secClass) ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Cannot query securely supported commands (${ getEnumMemberName( @@ -681,7 +732,7 @@ export class Security2CC extends CommandClass { continue; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Querying securely supported commands (${ getEnumMemberName( @@ -718,7 +769,7 @@ export class Security2CC extends CommandClass { ) { if (attempts < MAX_ATTEMPTS) { // We definitely know the highest security class - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Querying securely supported commands (${ getEnumMemberName( @@ -731,7 +782,7 @@ export class Security2CC extends CommandClass { await wait(500); continue; } else if (endpoint.index > 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Querying securely supported commands (${ getEnumMemberName( @@ -750,7 +801,7 @@ export class Security2CC extends CommandClass { break; } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Querying securely supported commands (${ getEnumMemberName( @@ -777,7 +828,7 @@ export class Security2CC extends CommandClass { // unless we were sure about the security class node.setSecurityClass(secClass, false); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `The node was NOT granted the security class ${ getEnumMemberName( SecurityClass, @@ -794,7 +845,7 @@ export class Security2CC extends CommandClass { // Mark the security class as granted unless we were sure about the security class node.setSecurityClass(secClass, true); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `The node was granted the security class ${ getEnumMemberName( SecurityClass, @@ -820,7 +871,7 @@ export class Security2CC extends CommandClass { for (const cc of supportedCCs) { logLines.push(`· ${getCCName(cc)}`); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: logLines.join("\n"), direction: "inbound", @@ -844,7 +895,7 @@ export class Security2CC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } /** Tests if a command should be sent secure and thus requires encapsulation */ @@ -900,8 +951,9 @@ export class Security2CC extends CommandClass { /** Encapsulates a command that should be sent encrypted */ public static encapsulate( - host: ZWaveHost, cc: CommandClass, + ownNodeId: number, + securityManagers: SecurityManagers, options?: { securityClass?: SecurityClass; multicastOutOfSync?: boolean; @@ -933,9 +985,11 @@ export class Security2CC extends CommandClass { nodeId = cc.nodeId as number; } - const ret = new Security2CCMessageEncapsulation(host, { + const ret = new Security2CCMessageEncapsulation({ nodeId, + ownNodeId, encapsulated: cc, + securityManagers, securityClass: options?.securityClass, extensions, verifyDelivery: options?.verifyDelivery, @@ -954,6 +1008,8 @@ export class Security2CC extends CommandClass { export interface Security2CCMessageEncapsulationOptions extends CCCommandOptions { + ownNodeId: number; + securityManagers: Readonly; /** Can be used to override the default security class for the command */ securityClass?: SecurityClass; extensions?: Security2Extension[]; @@ -1027,16 +1083,14 @@ export type MulticastContext = ) export class Security2CCMessageEncapsulation extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCMessageEncapsulationOptions, ) { - super(host, options); + super(options); // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; + this.securityManager = this.assertSecurity(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -1045,7 +1099,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { const sendingNodeId = this.nodeId as number; // Ensure the node has a security class - const securityClass = this.host.getHighestSecurityClass( + const securityClass = options.context.getHighestSecurityClass( sendingNodeId, ); validatePayload.withReason("No security class granted")( @@ -1130,8 +1184,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { const ctx = ((): MulticastContext => { const multicastGroupId = this.getMulticastGroupId(); if ( - options.frameType === "multicast" - || options.frameType === "broadcast" + options.context.frameType === "multicast" + || options.context.frameType === "broadcast" ) { if (multicastGroupId == undefined) { validatePayload.fail( @@ -1176,6 +1230,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { // Don't accept duplicate Singlecast commands prevSequenceNumber = validateSequenceNumber.call( this, + this.securityManager, this._sequenceNumber, ); @@ -1201,8 +1256,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { const authData = getAuthenticationData( sendingNodeId, - this.getDestinationIDRX(), - this.host.homeId, + this.getDestinationIDRX(options.context.ownNodeId), + options.context.homeId, messageLength, unencryptedPayload, ); @@ -1245,6 +1300,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { decrypt = () => this.decryptSinglecast( + options.context, sendingNodeId, prevSequenceNumber!, ciphertext, @@ -1344,11 +1400,11 @@ export class Security2CCMessageEncapsulation extends Security2CC { // make sure this contains a complete CC command that's worth splitting validatePayload(decryptedCCBytes.length >= 2); // and deserialize the CC - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: decryptedCCBytes, fromEncapsulation: true, encapCC: this, - frameType: options.frameType, + context: options.context, }); } this.plaintext = decryptedCCBytes; @@ -1439,8 +1495,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { return ret; } - private getDestinationIDRX(): number { - if (this.isSinglecast()) return this.host.ownNodeId; + private getDestinationIDRX(ownNodeId: number): number { + if (this.isSinglecast()) return ownNodeId; const ret = this.getMulticastGroupId(); if (ret == undefined) { @@ -1481,7 +1537,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { return spanExtension?.senderEI; } - private maybeAddSPANExtension(): void { + private maybeAddSPANExtension(ctx: CCEncodingContext): void { if (!this.isSinglecast()) return; const receiverNodeId: number = this.nodeId; @@ -1518,7 +1574,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { ); } else { const securityClass = this.securityClass - ?? this.host.getHighestSecurityClass(receiverNodeId); + ?? ctx.getHighestSecurityClass(receiverNodeId); if (securityClass == undefined) { throw new ZWaveError( @@ -1547,9 +1603,9 @@ export class Security2CCMessageEncapsulation extends Security2CC { } } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Include Sender EI in the command if we only have the receiver's EI - this.maybeAddSPANExtension(); + this.maybeAddSPANExtension(ctx); const unencryptedExtensions = this.extensions.filter( (e) => !e.isEncrypted(), @@ -1568,7 +1624,8 @@ export class Security2CCMessageEncapsulation extends Security2CC { e.serialize(index < unencryptedExtensions.length - 1) ), ]); - const serializedCC = this.encapsulated?.serialize() ?? Buffer.from([]); + const serializedCC = this.encapsulated?.serialize(ctx) + ?? Buffer.from([]); const plaintextPayload = Buffer.concat([ ...encryptedExtensions.map((e, index) => e.serialize(index < encryptedExtensions.length - 1) @@ -1581,9 +1638,9 @@ export class Security2CCMessageEncapsulation extends Security2CC { const messageLength = this.computeEncapsulationOverhead() + serializedCC.length; const authData = getAuthenticationData( - this.host.ownNodeId, + ctx.ownNodeId, destinationTag, - this.host.homeId, + ctx.homeId, messageLength, unencryptedPayload, ); @@ -1633,7 +1690,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { ciphertextPayload, authTag, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -1654,7 +1711,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { ); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "sequence number": this.sequenceNumber, }; @@ -1708,12 +1765,13 @@ export class Security2CCMessageEncapsulation extends Security2CC { } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } private decryptSinglecast( + ctx: CCParsingContext, sendingNodeId: number, prevSequenceNumber: number, ciphertext: Buffer, @@ -1754,7 +1812,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { && prevSequenceNumber != undefined && this["_sequenceNumber"] === ((prevSequenceNumber + 1) & 0xff) // And in case of a mock-based test, do this only on the controller - && !this.host.__internalIsMockNode + && !ctx.__internalIsMockNode ) { const nonce = spanState.currentSPAN.nonce; spanState.currentSPAN = undefined; @@ -1824,10 +1882,10 @@ export class Security2CCMessageEncapsulation extends Security2CC { // Try all security classes where we do not definitely know that it was not granted // While bootstrapping a node, we consider the key that is being exchanged (including S0) to be the highest. No need to look at others const possibleSecurityClasses = isBootstrappingNode - ? [this.host.getHighestSecurityClass(sendingNodeId)!] + ? [ctx.getHighestSecurityClass(sendingNodeId)!] : securityClassOrder.filter( (s) => - this.host.hasSecurityClass(sendingNodeId, s) + ctx.hasSecurityClass(sendingNodeId, s) !== false, ); @@ -1854,14 +1912,10 @@ export class Security2CCMessageEncapsulation extends Security2CC { if (ret.authOK) { // Also if we weren't sure before, we now know that the security class is granted if ( - this.host.hasSecurityClass(sendingNodeId, secClass) + ctx.hasSecurityClass(sendingNodeId, secClass) === undefined ) { - this.host.setSecurityClass( - sendingNodeId, - secClass, - true, - ); + ctx.setSecurityClass(sendingNodeId, secClass, true); } return { ...ret, @@ -1911,36 +1965,44 @@ export class Security2CCMessageEncapsulation extends Security2CC { // @publicAPI export type Security2CCNonceReportOptions = - | { - MOS: boolean; - SOS: true; - receiverEI: Buffer; - } - | { - MOS: true; - SOS: false; - receiverEI?: undefined; - }; + & { + ownNodeId: number; + securityManagers: SecurityManagers; + } + & ( + | { + MOS: boolean; + SOS: true; + receiverEI: Buffer; + } + | { + MOS: true; + SOS: false; + receiverEI?: undefined; + } + ); @CCCommand(Security2Command.NonceReport) export class Security2CCNonceReport extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & Security2CCNonceReportOptions), ) { - super(host, options); + super(options); // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; + this.securityManager = this.assertSecurity(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this._sequenceNumber = this.payload[0]; // Don't accept duplicate commands - validateSequenceNumber.call(this, this._sequenceNumber); + validateSequenceNumber.call( + this, + this.securityManager, + this._sequenceNumber, + ); this.MOS = !!(this.payload[1] & 0b10); this.SOS = !!(this.payload[1] & 0b1); @@ -1965,7 +2027,7 @@ export class Security2CCNonceReport extends Security2CC { } } - private securityManager: SecurityManager2; + private securityManager!: SecurityManager2; private _sequenceNumber: number | undefined; /** * Return the sequence number of this command. @@ -1987,7 +2049,7 @@ export class Security2CCNonceReport extends Security2CC { public readonly MOS: boolean; public readonly receiverEI?: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.sequenceNumber, (this.MOS ? 0b10 : 0) + (this.SOS ? 0b1 : 0), @@ -1995,10 +2057,10 @@ export class Security2CCNonceReport extends Security2CC { if (this.SOS) { this.payload = Buffer.concat([this.payload, this.receiverEI!]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "sequence number": this.sequenceNumber, SOS: this.SOS, @@ -2008,36 +2070,49 @@ export class Security2CCNonceReport extends Security2CC { message["receiver entropy"] = buffer2hex(this.receiverEI); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface Security2CCNonceGetOptions { + ownNodeId: number; + securityManagers: Readonly; +} + @CCCommand(Security2Command.NonceGet) @expectedCCResponse(Security2CCNonceReport) export class Security2CCNonceGet extends Security2CC { // TODO: A node sending this command MUST accept a delay up to + // 250 ms before receiving the Security 2 Nonce Report Command. - public constructor(host: ZWaveHost, options: CCCommandOptions) { - super(host, options); + public constructor( + options: + | CommandClassDeserializationOptions + | (CCCommandOptions & Security2CCNonceGetOptions), + ) { + super(options); // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; + this.securityManager = this.assertSecurity(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this._sequenceNumber = this.payload[0]; // Don't accept duplicate commands - validateSequenceNumber.call(this, this._sequenceNumber); + validateSequenceNumber.call( + this, + this.securityManager, + this._sequenceNumber, + ); } else { // No options here } } - private securityManager: SecurityManager2; + private securityManager!: SecurityManager2; private _sequenceNumber: number | undefined; /** * Return the sequence number of this command. @@ -2055,14 +2130,14 @@ export class Security2CCNonceGet extends Security2CC { return this._sequenceNumber; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sequenceNumber]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "sequence number": this.sequenceNumber }, }; } @@ -2081,12 +2156,11 @@ export interface Security2CCKEXReportOptions { @CCCommand(Security2Command.KEXReport) export class Security2CCKEXReport extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & Security2CCKEXReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.requestCSA = !!(this.payload[0] & 0b10); @@ -2123,7 +2197,7 @@ export class Security2CCKEXReport extends Security2CC { public readonly supportedECDHProfiles: readonly ECDHProfiles[]; public readonly requestedKeys: readonly SecurityClass[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ this._reserved @@ -2143,12 +2217,12 @@ export class Security2CCKEXReport extends Security2CC { SecurityClass.S2_Unauthenticated, ), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { echo: this.echo, "supported schemes": this.supportedKEXSchemes @@ -2206,12 +2280,11 @@ function testExpectedResponseForKEXSet( @expectedCCResponse(getExpectedResponseForKEXSet, testExpectedResponseForKEXSet) export class Security2CCKEXSet extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & Security2CCKEXSetOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this._reserved = this.payload[0] & 0b1111_1100; @@ -2253,7 +2326,7 @@ export class Security2CCKEXSet extends Security2CC { public selectedECDHProfile: ECDHProfiles; public grantedKeys: SecurityClass[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ this._reserved @@ -2273,12 +2346,12 @@ export class Security2CCKEXSet extends Security2CC { SecurityClass.S2_Unauthenticated, ), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { echo: this.echo, "selected scheme": getEnumMemberName( @@ -2306,10 +2379,9 @@ export interface Security2CCKEXFailOptions extends CCCommandOptions { @CCCommand(Security2Command.KEXFail) export class Security2CCKEXFail extends Security2CC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | Security2CCKEXFailOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.failType = this.payload[0]; @@ -2320,14 +2392,14 @@ export class Security2CCKEXFail extends Security2CC { public failType: KEXFailType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.failType]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { reason: getEnumMemberName(KEXFailType, this.failType) }, }; } @@ -2342,12 +2414,11 @@ export interface Security2CCPublicKeyReportOptions extends CCCommandOptions { @CCCommand(Security2Command.PublicKeyReport) export class Security2CCPublicKeyReport extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCPublicKeyReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 17); this.includingNode = !!(this.payload[0] & 0b1); @@ -2361,17 +2432,17 @@ export class Security2CCPublicKeyReport extends Security2CC { public includingNode: boolean; public publicKey: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.includingNode ? 1 : 0]), this.publicKey, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "is including node": this.includingNode, "public key": buffer2hex(this.publicKey), @@ -2389,12 +2460,11 @@ export interface Security2CCNetworkKeyReportOptions extends CCCommandOptions { @CCCommand(Security2Command.NetworkKeyReport) export class Security2CCNetworkKeyReport extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCNetworkKeyReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 17); this.grantedKey = bitMaskToSecurityClass(this.payload, 0); @@ -2408,17 +2478,17 @@ export class Security2CCNetworkKeyReport extends Security2CC { public grantedKey: SecurityClass; public networkKey: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ securityClassToBitMask(this.grantedKey), this.networkKey, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "security class": getEnumMemberName( SecurityClass, @@ -2441,12 +2511,11 @@ export interface Security2CCNetworkKeyGetOptions extends CCCommandOptions { // FIXME: maybe use the dynamic @expectedCCResponse instead? export class Security2CCNetworkKeyGet extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCNetworkKeyGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.requestedKey = bitMaskToSecurityClass(this.payload, 0); @@ -2457,14 +2526,14 @@ export class Security2CCNetworkKeyGet extends Security2CC { public requestedKey: SecurityClass; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = securityClassToBitMask(this.requestedKey); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "security class": getEnumMemberName( SecurityClass, @@ -2487,12 +2556,11 @@ export interface Security2CCTransferEndOptions extends CCCommandOptions { @CCCommand(Security2Command.TransferEnd) export class Security2CCTransferEnd extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCTransferEndOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.keyVerified = !!(this.payload[0] & 0b10); @@ -2506,16 +2574,16 @@ export class Security2CCTransferEnd extends Security2CC { public keyVerified: boolean; public keyRequestComplete: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ (this.keyVerified ? 0b10 : 0) + (this.keyRequestComplete ? 0b1 : 0), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "key verified": this.keyVerified, "request complete": this.keyRequestComplete, @@ -2534,12 +2602,11 @@ export interface Security2CCCommandsSupportedReportOptions @CCCommand(Security2Command.CommandsSupportedReport) export class Security2CCCommandsSupportedReport extends Security2CC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | Security2CCCommandsSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const CCs = parseCCList(this.payload); // SDS13783: A sending node MAY terminate the list of supported command classes with the @@ -2554,14 +2621,14 @@ export class Security2CCCommandsSupportedReport extends Security2CC { public readonly supportedCCs: CommandClasses[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeCCList(this.supportedCCs, []); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported CCs": this.supportedCCs .map((cc) => getCCName(cc)) diff --git a/packages/cc/src/cc/SecurityCC.ts b/packages/cc/src/cc/SecurityCC.ts index c3757a36b13a..d569657858db 100644 --- a/packages/cc/src/cc/SecurityCC.ts +++ b/packages/cc/src/cc/SecurityCC.ts @@ -22,9 +22,9 @@ import { } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { buffer2hex, num2hex, pick } from "@zwave-js/shared/safe"; import { wait } from "alcalzone-shared/async"; @@ -34,6 +34,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -112,11 +113,13 @@ export class SecurityCCAPI extends PhysicalCCAPI { requestNextNonce ? SecurityCCCommandEncapsulationNonceGet : SecurityCCCommandEncapsulation - )(this.applHost, { + )({ nodeId: this.endpoint.nodeId, + ownNodeId: this.host.ownNodeId, + securityManager: this.host.securityManager!, encapsulated, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -125,11 +128,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { public async getNonce(): Promise { this.assertSupportsCommand(SecurityCommand, SecurityCommand.NonceGet); - const cc = new SecurityCCNonceGet(this.applHost, { + const cc = new SecurityCCNonceGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, { ...this.commandOptions, @@ -140,13 +143,13 @@ export class SecurityCCAPI extends PhysicalCCAPI { if (!response) return; const nonce = response.nonce; - const secMan = this.applHost.securityManager!; + const secMan = this.host.securityManager!; secMan.setNonce( { issuer: this.endpoint.nodeId, nonceId: secMan.getNonceId(nonce), }, - { nonce, receiver: this.applHost.ownNodeId }, + { nonce, receiver: this.host.ownNodeId }, { free: true }, ); return nonce; @@ -162,27 +165,27 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.NonceReport, ); - if (!this.applHost.securityManager) { + if (!this.host.securityManager) { throw new ZWaveError( `Nonces can only be sent if secure communication is set up!`, ZWaveErrorCodes.Driver_NoSecurity, ); } - const nonce = this.applHost.securityManager.generateNonce( + const nonce = this.host.securityManager.generateNonce( this.endpoint.nodeId, HALF_NONCE_SIZE, ); - const nonceId = this.applHost.securityManager.getNonceId(nonce); + const nonceId = this.host.securityManager.getNonceId(nonce); - const cc = new SecurityCCNonceReport(this.applHost, { + const cc = new SecurityCCNonceReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, nonce, }); try { - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Seems we need these options or some nodes won't accept the nonce transmitOptions: TransmitOptions.ACK @@ -197,7 +200,7 @@ export class SecurityCCAPI extends PhysicalCCAPI { } catch (e) { if (isTransmissionError(e)) { // The nonce could not be sent, invalidate it - this.applHost.securityManager.deleteNonce(nonceId); + this.host.securityManager.deleteNonce(nonceId); return false; } else { // Pass other errors through @@ -210,11 +213,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { public async getSecurityScheme(): Promise<[0]> { this.assertSupportsCommand(SecurityCommand, SecurityCommand.SchemeGet); - const cc = new SecurityCCSchemeGet(this.applHost, { + const cc = new SecurityCCSchemeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); // There is only one scheme, so we hardcode it return [0]; } @@ -225,18 +228,20 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.SchemeReport, ); - let cc: CommandClass = new SecurityCCSchemeReport(this.applHost, { + let cc: CommandClass = new SecurityCCSchemeReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); if (encapsulated) { - cc = new SecurityCCCommandEncapsulation(this.applHost, { + cc = new SecurityCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, + ownNodeId: this.host.ownNodeId, + securityManager: this.host.securityManager!, encapsulated: cc, }); } - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async inheritSecurityScheme(): Promise { @@ -245,11 +250,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.SchemeInherit, ); - const cc = new SecurityCCSchemeInherit(this.applHost, { + const cc = new SecurityCCSchemeInherit({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); // There is only one scheme, so we don't return anything here } @@ -259,18 +264,20 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.NetworkKeySet, ); - const keySet = new SecurityCCNetworkKeySet(this.applHost, { + const keySet = new SecurityCCNetworkKeySet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, networkKey, }); - const cc = new SecurityCCCommandEncapsulation(this.applHost, { + const cc = new SecurityCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, + ownNodeId: this.host.ownNodeId, + securityManager: this.host.securityManager!, encapsulated: keySet, alternativeNetworkKey: Buffer.alloc(16, 0), }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async verifyNetworkKey(): Promise { @@ -279,11 +286,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.NetworkKeyVerify, ); - const cc = new SecurityCCNetworkKeyVerify(this.applHost, { + const cc = new SecurityCCNetworkKeyVerify({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -293,11 +300,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.CommandsSupportedGet, ); - const cc = new SecurityCCCommandsSupportedGet(this.applHost, { + const cc = new SecurityCCCommandsSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SecurityCCCommandsSupportedReport >( cc, @@ -317,13 +324,13 @@ export class SecurityCCAPI extends PhysicalCCAPI { SecurityCommand.CommandsSupportedReport, ); - const cc = new SecurityCCCommandsSupportedReport(this.applHost, { + const cc = new SecurityCCCommandsSupportedReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, supportedCCs, controlledCCs, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -333,23 +340,57 @@ export class SecurityCC extends CommandClass { declare ccCommand: SecurityCommand; // Force singlecast for the Security CC declare nodeId: number; - // Define the securityManager as existing - declare host: ZWaveHost & { - securityManager: SecurityManager; - }; - - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + + protected assertSecurity( + options: + | (CCCommandOptions & { + ownNodeId: number; + securityManager: SecurityManager; + }) + | CommandClassDeserializationOptions, + ): SecurityManager { + const verb = gotDeserializationOptions(options) ? "decoded" : "sent"; + const ownNodeId = gotDeserializationOptions(options) + ? options.context.ownNodeId + : options.ownNodeId; + if (!ownNodeId) { + throw new ZWaveError( + `Secure commands (S0) can only be ${verb} when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + let ret: SecurityManager | undefined; + if (gotDeserializationOptions(options)) { + ret = options.context.securityManager; + } else { + ret = options.securityManager; + } + + if (!ret) { + throw new ZWaveError( + `Secure commands (S0) can only be ${verb} when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } + + return ret; + } + + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Security, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "Querying securely supported commands (S0)...", direction: "outbound", }); @@ -366,7 +407,7 @@ export class SecurityCC extends CommandClass { controlledCCs = resp.controlledCCs; break; } else if (attempts < MAX_ATTEMPTS) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying securely supported commands (S0), attempt ${attempts}/${MAX_ATTEMPTS} failed. Retrying in 500ms...`, @@ -378,7 +419,7 @@ export class SecurityCC extends CommandClass { if (!supportedCCs || !controlledCCs) { if (node.hasSecurityClass(SecurityClass.S0_Legacy) === true) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying securely supported commands (S0) failed", level: "warn", @@ -387,7 +428,7 @@ export class SecurityCC extends CommandClass { } else { // We didn't know if the node was secure and it didn't respond, // assume that it doesn't have the S0 security class - applHost.controllerLog.logNode( + ctx.logNode( node.id, `The node was not granted the S0 security class. Continuing interview non-securely.`, ); @@ -407,7 +448,7 @@ export class SecurityCC extends CommandClass { for (const cc of controlledCCs) { logLines.push(`· ${getCCName(cc)}`); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logLines.join("\n"), direction: "inbound", }); @@ -443,14 +484,14 @@ export class SecurityCC extends CommandClass { // We know for sure that the node is included securely if (node.hasSecurityClass(SecurityClass.S0_Legacy) !== true) { node.setSecurityClass(SecurityClass.S0_Legacy, true); - applHost.controllerLog.logNode( + ctx.logNode( node.id, `The node was granted the S0 security class`, ); } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } /** Tests if a command should be sent secure and thus requires encapsulation */ @@ -494,12 +535,15 @@ export class SecurityCC extends CommandClass { /** Encapsulates a command that should be sent encrypted */ public static encapsulate( - host: ZWaveHost, + ownNodeId: number, + securityManager: SecurityManager, cc: CommandClass, ): SecurityCCCommandEncapsulation { // TODO: When to return a SecurityCCCommandEncapsulationNonceGet? - const ret = new SecurityCCCommandEncapsulation(host, { + const ret = new SecurityCCCommandEncapsulation({ nodeId: cc.nodeId, + ownNodeId, + securityManager, encapsulated: cc, }); @@ -519,12 +563,11 @@ interface SecurityCCNonceReportOptions extends CCCommandOptions { @CCCommand(SecurityCommand.NonceReport) export class SecurityCCNonceReport extends SecurityCC { constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SecurityCCNonceReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload.withReason("Invalid nonce length")( this.payload.length === HALF_NONCE_SIZE, @@ -543,14 +586,14 @@ export class SecurityCCNonceReport extends SecurityCC { public nonce: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = this.nonce; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { nonce: buffer2hex(this.nonce) }, }; } @@ -564,6 +607,8 @@ export class SecurityCCNonceGet extends SecurityCC {} export interface SecurityCCCommandEncapsulationOptions extends CCCommandOptions { + ownNodeId: number; + securityManager: SecurityManager; encapsulated: CommandClass; alternativeNetworkKey?: Buffer; } @@ -583,25 +628,13 @@ function getCCResponseForCommandEncapsulation( ) export class SecurityCCCommandEncapsulation extends SecurityCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SecurityCCCommandEncapsulationOptions, ) { - super(host, options); + super(options); - const verb = gotDeserializationOptions(options) ? "decoded" : "sent"; - if (!(this.host.ownNodeId as unknown)) { - throw new ZWaveError( - `Secure commands (S0) can only be ${verb} when the controller's node id is known!`, - ZWaveErrorCodes.Driver_NotReady, - ); - } else if (!(this.host.securityManager as unknown)) { - throw new ZWaveError( - `Secure commands (S0) can only be ${verb} when the network key for the applHost is set`, - ZWaveErrorCodes.Driver_NoSecurity, - ); - } + this.securityManager = this.assertSecurity(options); if (gotDeserializationOptions(options)) { // HALF_NONCE_SIZE bytes iv, 1 byte frame control, at least 1 CC byte, 1 byte nonce id, 8 bytes auth code @@ -614,7 +647,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { const authCode = this.payload.subarray(-8); // Retrieve the used nonce from the nonce store - const nonce = this.host.securityManager.getNonce(nonceId); + const nonce = this.securityManager.getNonce(nonceId); // Only accept the message if the nonce hasn't expired validatePayload.withReason( `Nonce ${ @@ -624,10 +657,10 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { } expired, cannot decode security encapsulated command.`, )(!!nonce); // and mark the nonce as used - this.host.securityManager.deleteNonce(nonceId); + this.securityManager.deleteNonce(nonceId); - this.authKey = this.host.securityManager.authKey; - this.encryptionKey = this.host.securityManager.encryptionKey; + this.authKey = this.securityManager.authKey; + this.encryptionKey = this.securityManager.encryptionKey; // Validate the encrypted data const authData = getAuthenticationData( @@ -635,7 +668,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { nonce!, this.ccCommand, this.nodeId, - this.host.ownNodeId, + options.context.ownNodeId, encryptedPayload, ); const expectedAuthCode = computeMAC(authData, this.authKey); @@ -670,12 +703,14 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { options.alternativeNetworkKey, ); } else { - this.authKey = this.host.securityManager.authKey; - this.encryptionKey = this.host.securityManager.encryptionKey; + this.authKey = this.securityManager.authKey; + this.encryptionKey = this.securityManager.encryptionKey; } } } + private securityManager: SecurityManager; + private sequenced: boolean | undefined; private secondFrame: boolean | undefined; private sequenceCounter: number | undefined; @@ -688,7 +723,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { public get nonceId(): number | undefined { if (!this.nonce) return undefined; - return this.host.securityManager.getNonceId(this.nonce); + return this.securityManager.getNonceId(this.nonce); } public nonce: Buffer | undefined; @@ -718,8 +753,8 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: SecurityCCCommandEncapsulation[], + ctx: CCParsingContext, ): void { // Concat the CC buffers this.decryptedCCBytes = Buffer.concat( @@ -728,20 +763,21 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { // make sure this contains a complete CC command that's worth splitting validatePayload(this.decryptedCCBytes.length >= 2); // and deserialize the CC - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: this.decryptedCCBytes, fromEncapsulation: true, encapCC: this, + context: ctx, }); } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (!this.nonce) throwNoNonce(); if (this.nonce.length !== HALF_NONCE_SIZE) { throwNoNonce("Invalid nonce size"); } - const serializedCC = this.encapsulated.serialize(); + const serializedCC = this.encapsulated.serialize(ctx); const plaintext = Buffer.concat([ Buffer.from([0]), // TODO: frame control serializedCC, @@ -755,7 +791,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { senderNonce, this.nonce, this.ccCommand, - this.host.ownNodeId, + ctx.ownNodeId, this.nodeId, ciphertext, ); @@ -773,7 +809,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { Buffer.from([this.nonceId!]), authCode, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -781,7 +817,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { return super.computeEncapsulationOverhead() + 18; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.nonceId != undefined) { message["nonce id"] = this.nonceId; @@ -818,7 +854,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -833,25 +869,24 @@ export class SecurityCCCommandEncapsulationNonceGet @CCCommand(SecurityCommand.SchemeReport) export class SecurityCCSchemeReport extends SecurityCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); // The including controller MUST NOT perform any validation of the Supported Security Schemes byte } } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) this.payload = Buffer.from([0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), // Hide the default payload line message: undefined, }; @@ -862,22 +897,21 @@ export class SecurityCCSchemeReport extends SecurityCC { @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeGet extends SecurityCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); // Don't care, we won't get sent this and we have no options } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) this.payload = Buffer.from([0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), // Hide the default payload line message: undefined, }; @@ -888,22 +922,21 @@ export class SecurityCCSchemeGet extends SecurityCC { @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeInherit extends SecurityCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); // Don't care, we won't get sent this and we have no options } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Since it is unlikely that any more schemes will be added to S0, we hardcode the default scheme here (bit 0 = 0) this.payload = Buffer.from([0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), // Hide the default payload line message: undefined, }; @@ -922,12 +955,11 @@ export interface SecurityCCNetworkKeySetOptions extends CCCommandOptions { @expectedCCResponse(SecurityCCNetworkKeyVerify) export class SecurityCCNetworkKeySet extends SecurityCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SecurityCCNetworkKeySetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 16); this.networkKey = this.payload.subarray(0, 16); @@ -944,14 +976,14 @@ export class SecurityCCNetworkKeySet extends SecurityCC { public networkKey: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = this.networkKey; - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(applHost: ZWaveApplicationHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { // The network key shouldn't be logged, so users can safely post their logs online - const { message, ...log } = super.toLogEntry(applHost); + const { message, ...log } = super.toLogEntry(ctx); return log; } } @@ -967,12 +999,11 @@ export interface SecurityCCCommandsSupportedReportOptions @CCCommand(SecurityCommand.CommandsSupportedReport) export class SecurityCCCommandsSupportedReport extends SecurityCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SecurityCCCommandsSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -990,12 +1021,12 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { public readonly reportsToFollow: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.reportsToFollow]), encodeCCList(this.supportedCCs, this.controlledCCs), ]); - return super.serialize(); + return super.serialize(ctx); } public getPartialCCSessionId(): Record | undefined { @@ -1011,7 +1042,6 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { public controlledCCs: CommandClasses[]; public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: SecurityCCCommandsSupportedReport[], ): void { // Concat the lists of CCs @@ -1023,9 +1053,9 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { reportsToFollow: this.reportsToFollow, supportedCCs: this.supportedCCs diff --git a/packages/cc/src/cc/SoundSwitchCC.ts b/packages/cc/src/cc/SoundSwitchCC.ts index 91419b2b92e3..8dda10b755c8 100644 --- a/packages/cc/src/cc/SoundSwitchCC.ts +++ b/packages/cc/src/cc/SoundSwitchCC.ts @@ -11,11 +11,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp } from "alcalzone-shared/math"; @@ -33,6 +29,7 @@ import { type CCResponsePredicate, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -119,11 +116,11 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.TonesNumberGet, ); - const cc = new SoundSwitchCCTonesNumberGet(this.applHost, { + const cc = new SoundSwitchCCTonesNumberGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCTonesNumberReport >( cc, @@ -140,12 +137,12 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.ToneInfoGet, ); - const cc = new SoundSwitchCCToneInfoGet(this.applHost, { + const cc = new SoundSwitchCCToneInfoGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, toneId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCToneInfoReport >( cc, @@ -164,13 +161,13 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.ConfigurationSet, ); - const cc = new SoundSwitchCCConfigurationSet(this.applHost, { + const cc = new SoundSwitchCCConfigurationSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, defaultToneId, defaultVolume, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -180,11 +177,11 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.ConfigurationGet, ); - const cc = new SoundSwitchCCConfigurationGet(this.applHost, { + const cc = new SoundSwitchCCConfigurationGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCConfigurationReport >( cc, @@ -212,13 +209,13 @@ export class SoundSwitchCCAPI extends CCAPI { ); } - const cc = new SoundSwitchCCTonePlaySet(this.applHost, { + const cc = new SoundSwitchCCTonePlaySet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, toneId, volume, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async stopPlaying(): Promise { @@ -227,13 +224,13 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.TonePlaySet, ); - const cc = new SoundSwitchCCTonePlaySet(this.applHost, { + const cc = new SoundSwitchCCTonePlaySet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, toneId: 0x00, volume: 0x00, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -243,11 +240,11 @@ export class SoundSwitchCCAPI extends CCAPI { SoundSwitchCommand.TonePlayGet, ); - const cc = new SoundSwitchCCTonePlayGet(this.applHost, { + const cc = new SoundSwitchCCTonePlayGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCTonePlayReport >( cc, @@ -372,36 +369,38 @@ export class SoundSwitchCCAPI extends CCAPI { export class SoundSwitchCC extends CommandClass { declare ccCommand: SoundSwitchCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Sound Switch"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "requesting tone count...", direction: "outbound", }); const toneCount = await api.getToneCount(); if (toneCount != undefined) { const logMessage = `supports ${toneCount} tones`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying tone count timed out, skipping interview...", level: "warn", @@ -409,7 +408,7 @@ export class SoundSwitchCC extends CommandClass { return; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "requesting current sound configuration...", direction: "outbound", }); @@ -418,7 +417,7 @@ export class SoundSwitchCC extends CommandClass { const logMessage = `received current sound configuration: default tone ID: ${config.defaultToneId} default volume: ${config.defaultVolume}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -426,7 +425,7 @@ default volume: ${config.defaultVolume}`; const metadataStates: Record = {}; for (let toneId = 1; toneId <= toneCount; toneId++) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: `requesting info for tone #${toneId}`, direction: "outbound", }); @@ -435,7 +434,7 @@ default volume: ${config.defaultVolume}`; const logMessage = `received info for tone #${toneId}: name: ${info.name} duration: ${info.duration} seconds`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -443,7 +442,7 @@ duration: ${info.duration} seconds`; } // Remember tone count and info on the default tone ID metadata - this.setMetadata(applHost, SoundSwitchCCValues.defaultToneId, { + this.setMetadata(ctx, SoundSwitchCCValues.defaultToneId, { ...SoundSwitchCCValues.defaultToneId.meta, min: 1, max: toneCount, @@ -451,7 +450,7 @@ duration: ${info.duration} seconds`; }); // Remember tone count and info on the tone ID metadata - this.setMetadata(applHost, SoundSwitchCCValues.toneId, { + this.setMetadata(ctx, SoundSwitchCCValues.toneId, { ...SoundSwitchCCValues.toneId.meta, min: 0, max: toneCount, @@ -463,7 +462,7 @@ duration: ${info.duration} seconds`; }); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -477,12 +476,11 @@ export interface SoundSwitchCCTonesNumberReportOptions @CCCommand(SoundSwitchCommand.TonesNumberReport) export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SoundSwitchCCTonesNumberReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.toneCount = this.payload[0]; @@ -493,14 +491,14 @@ export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { public toneCount: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.toneCount]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "# of tones": this.toneCount }, }; } @@ -520,12 +518,11 @@ export interface SoundSwitchCCToneInfoReportOptions extends CCCommandOptions { @CCCommand(SoundSwitchCommand.ToneInfoReport) export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SoundSwitchCCToneInfoReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.toneId = this.payload[0]; @@ -546,18 +543,18 @@ export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { public readonly duration: number; public readonly name: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.toneId, 0, 0, this.name.length]), Buffer.from(this.name, "utf8"), ]); this.payload.writeUInt16BE(this.duration, 1); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "tone id": this.toneId, duration: `${this.duration} seconds`, @@ -586,12 +583,11 @@ export interface SoundSwitchCCToneInfoGetOptions extends CCCommandOptions { ) export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SoundSwitchCCToneInfoGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.toneId = this.payload[0]; @@ -602,14 +598,14 @@ export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { public toneId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.toneId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "tone id": this.toneId }, }; } @@ -625,12 +621,11 @@ export interface SoundSwitchCCConfigurationSetOptions extends CCCommandOptions { @useSupervision() export class SoundSwitchCCConfigurationSet extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | SoundSwitchCCConfigurationSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.defaultVolume = this.payload[0]; @@ -644,14 +639,14 @@ export class SoundSwitchCCConfigurationSet extends SoundSwitchCC { public defaultVolume: number; public defaultToneId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.defaultVolume, this.defaultToneId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "default volume": `${this.defaultVolume} %`, "default tone id": this.defaultToneId, @@ -669,12 +664,11 @@ export interface SoundSwitchCCConfigurationReportOptions { @CCCommand(SoundSwitchCommand.ConfigurationReport) export class SoundSwitchCCConfigurationReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & SoundSwitchCCConfigurationReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.defaultVolume = clamp(this.payload[0], 0, 100); @@ -691,14 +685,14 @@ export class SoundSwitchCCConfigurationReport extends SoundSwitchCC { @ccValue(SoundSwitchCCValues.defaultToneId) public defaultToneId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.defaultVolume, this.defaultToneId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "default volume": `${this.defaultVolume} %`, "default tone id": this.defaultToneId, @@ -722,12 +716,11 @@ export interface SoundSwitchCCTonePlaySetOptions { @useSupervision() export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & SoundSwitchCCTonePlaySetOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.toneId = this.payload[0]; @@ -743,12 +736,12 @@ export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { public toneId: ToneId | number; public volume?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.toneId, this.volume ?? 0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "tone id": this.toneId, }; @@ -756,7 +749,7 @@ export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { message.volume = this.volume === 0 ? "default" : `${this.volume} %`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -772,12 +765,11 @@ export interface SoundSwitchCCTonePlayReportOptions { @CCCommand(SoundSwitchCommand.TonePlayReport) export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & SoundSwitchCCTonePlayReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.toneId = this.payload[0]; @@ -796,12 +788,12 @@ export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { @ccValue(SoundSwitchCCValues.volume) public volume?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.toneId, this.volume ?? 0]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "tone id": this.toneId, }; @@ -809,7 +801,7 @@ export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { message.volume = this.volume === 0 ? "default" : `${this.volume} %`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/SupervisionCC.ts b/packages/cc/src/cc/SupervisionCC.ts index 10f6d69c4a77..4faea6a5614f 100644 --- a/packages/cc/src/cc/SupervisionCC.ts +++ b/packages/cc/src/cc/SupervisionCC.ts @@ -2,14 +2,17 @@ import { CommandClasses, Duration, EncapsulationFlags, - type IZWaveEndpoint, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, type SinglecastCC, type SupervisionResult, SupervisionStatus, + type SupportsCC, TransmitOptions, ZWaveError, ZWaveErrorCodes, @@ -17,9 +20,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetNode, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { PhysicalCCAPI } from "../lib/API"; @@ -88,7 +91,7 @@ export class SupervisionCCAPI extends PhysicalCCAPI { lowPriority = false, ...cmdOptions } = options; - const cc = new SupervisionCCReport(this.applHost, { + const cc = new SupervisionCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...cmdOptions, @@ -98,7 +101,7 @@ export class SupervisionCCAPI extends PhysicalCCAPI { cc.encapsulationFlags = encapsulationFlags; try { - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // Supervision Reports must be prioritized over normal messages priority: lowPriority @@ -140,8 +143,8 @@ export class SupervisionCC extends CommandClass { /** Encapsulates a command that targets a specific endpoint */ public static encapsulate( - host: ZWaveHost, cc: CommandClass, + sessionId: number, requestStatusUpdates: boolean = true, ): SupervisionCCGet { if (!cc.isSinglecast()) { @@ -151,11 +154,12 @@ export class SupervisionCC extends CommandClass { ); } - const ret = new SupervisionCCGet(host, { + const ret = new SupervisionCCGet({ nodeId: cc.nodeId, // Supervision CC is wrapped inside MultiChannel CCs, so the endpoint must be copied endpoint: cc.endpointIndex, encapsulated: cc, + sessionId, requestStatusUpdates, }); @@ -195,13 +199,13 @@ export class SupervisionCC extends CommandClass { * Returns whether a node supports the given CC with Supervision encapsulation. */ public static getCCSupportedWithSupervision( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ccId: CommandClasses, ): boolean { // By default assume supervision is supported for all CCs, unless we've remembered one not to be return ( - applHost + ctx .getValueDB(endpoint.nodeId) .getValue( SupervisionCCValues.ccSupported(ccId).endpoint( @@ -215,12 +219,12 @@ export class SupervisionCC extends CommandClass { * Remembers whether a node supports the given CC with Supervision encapsulation. */ public static setCCSupportedWithSupervision( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ccId: CommandClasses, supported: boolean, ): void { - applHost + ctx .getValueDB(endpoint.nodeId) .setValue( SupervisionCCValues.ccSupported(ccId).endpoint(endpoint.index), @@ -230,7 +234,11 @@ export class SupervisionCC extends CommandClass { /** Returns whether this is a valid command to send supervised */ public static mayUseSupervision( - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetNode< + NodeId & SupportsCC & GetEndpoint + >, command: T, ): command is SinglecastCC { // Supervision may only be used for singlecast CCs that expect no response @@ -239,9 +247,9 @@ export class SupervisionCC extends CommandClass { if (command.expectsCCResponse()) return false; // with a valid node and endpoint - const node = command.getNode(applHost); + const node = command.getNode(ctx); if (!node) return false; - const endpoint = command.getEndpoint(applHost); + const endpoint = command.getEndpoint(ctx); if (!endpoint) return false; // and only if ... @@ -252,7 +260,7 @@ export class SupervisionCC extends CommandClass { && shouldUseSupervision(command) // ... and we haven't previously determined that the node doesn't properly support it && SupervisionCC.getCCSupportedWithSupervision( - applHost, + ctx, endpoint, command.ccId, ) @@ -283,12 +291,11 @@ export type SupervisionCCReportOptions = @CCCommand(SupervisionCommand.Report) export class SupervisionCCReport extends SupervisionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & SupervisionCCReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); @@ -316,7 +323,7 @@ export class SupervisionCCReport extends SupervisionCC { public readonly status: SupervisionStatus; public readonly duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([ (this.moreUpdatesFollow ? 0b1_0_000000 : 0) @@ -332,10 +339,10 @@ export class SupervisionCCReport extends SupervisionCC { Buffer.from([this.duration.serializeReport()]), ]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "session id": this.sessionId, "more updates follow": this.moreUpdatesFollow, @@ -345,7 +352,7 @@ export class SupervisionCCReport extends SupervisionCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -368,6 +375,7 @@ export class SupervisionCCReport extends SupervisionCC { export interface SupervisionCCGetOptions extends CCCommandOptions { requestStatusUpdates: boolean; encapsulated: CommandClass; + sessionId: number; } function testResponseForSupervisionCCGet( @@ -381,23 +389,23 @@ function testResponseForSupervisionCCGet( @expectedCCResponse(SupervisionCCReport, testResponseForSupervisionCCGet) export class SupervisionCCGet extends SupervisionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | SupervisionCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.requestStatusUpdates = !!(this.payload[0] & 0b1_0_000000); this.sessionId = this.payload[0] & 0b111111; - this.encapsulated = CommandClass.from(this.host, { + this.encapsulated = CommandClass.from({ data: this.payload.subarray(2), fromEncapsulation: true, encapCC: this, origin: options.origin, + context: options.context, }); } else { - this.sessionId = host.getNextSupervisionSessionId(this.nodeId); + this.sessionId = options.sessionId; this.requestStatusUpdates = options.requestStatusUpdates; this.encapsulated = options.encapsulated; options.encapsulated.encapsulatingCC = this as any; @@ -408,8 +416,8 @@ export class SupervisionCCGet extends SupervisionCC { public sessionId: number; public encapsulated: CommandClass; - public serialize(): Buffer { - const encapCC = this.encapsulated.serialize(); + public serialize(ctx: CCEncodingContext): Buffer { + const encapCC = this.encapsulated.serialize(ctx); this.payload = Buffer.concat([ Buffer.from([ (this.requestStatusUpdates ? 0b10_000000 : 0) @@ -418,7 +426,7 @@ export class SupervisionCCGet extends SupervisionCC { ]), encapCC, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -426,9 +434,9 @@ export class SupervisionCCGet extends SupervisionCC { return super.computeEncapsulationOverhead() + 2; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "session id": this.sessionId, "request updates": this.requestStatusUpdates, diff --git a/packages/cc/src/cc/ThermostatFanModeCC.ts b/packages/cc/src/cc/ThermostatFanModeCC.ts index 451d2739ae07..0bace1013cbe 100644 --- a/packages/cc/src/cc/ThermostatFanModeCC.ts +++ b/packages/cc/src/cc/ThermostatFanModeCC.ts @@ -13,11 +13,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -33,6 +29,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -175,11 +174,11 @@ export class ThermostatFanModeCCAPI extends CCAPI { ThermostatFanModeCommand.Get, ); - const cc = new ThermostatFanModeCCGet(this.applHost, { + const cc = new ThermostatFanModeCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanModeCCReport >( cc, @@ -200,13 +199,13 @@ export class ThermostatFanModeCCAPI extends CCAPI { ThermostatFanModeCommand.Set, ); - const cc = new ThermostatFanModeCCSet(this.applHost, { + const cc = new ThermostatFanModeCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, mode, off, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupportedModes(): Promise< @@ -217,11 +216,11 @@ export class ThermostatFanModeCCAPI extends CCAPI { ThermostatFanModeCommand.SupportedGet, ); - const cc = new ThermostatFanModeCCSupportedGet(this.applHost, { + const cc = new ThermostatFanModeCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanModeCCSupportedReport >( cc, @@ -237,25 +236,27 @@ export class ThermostatFanModeCCAPI extends CCAPI { export class ThermostatFanModeCC extends CommandClass { declare ccCommand: ThermostatFanModeCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Fan Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // First query the possible modes to set the metadata - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported thermostat fan modes...", direction: "outbound", @@ -271,13 +272,13 @@ export class ThermostatFanModeCC extends CommandClass { ) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported thermostat fan modes timed out, skipping interview...", @@ -285,25 +286,27 @@ export class ThermostatFanModeCC extends CommandClass { return; } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Fan Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current thermostat fan mode...", direction: "outbound", @@ -319,7 +322,7 @@ export class ThermostatFanModeCC extends CommandClass { if (currentStatus.off != undefined) { logMessage += ` (turned off)`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -338,12 +341,11 @@ export type ThermostatFanModeCCSetOptions = CCCommandOptions & { @useSupervision() export class ThermostatFanModeCCSet extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatFanModeCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -359,20 +361,20 @@ export class ThermostatFanModeCCSet extends ThermostatFanModeCC { public mode: ThermostatFanMode; public off: boolean | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ (this.off ? 0b1000_0000 : 0) | (this.mode & 0b1111), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { mode: getEnumMemberName(ThermostatFanMode, this.mode), }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -381,17 +383,15 @@ export class ThermostatFanModeCCSet extends ThermostatFanModeCC { @CCCommand(ThermostatFanModeCommand.Report) export class ThermostatFanModeCCReport extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.mode = this.payload[0] & 0b1111; - if (this.version >= 3) { - this.off = !!(this.payload[0] & 0b1000_0000); - } + // V3+ + this.off = !!(this.payload[0] & 0b1000_0000); } @ccValue(ThermostatFanModeCCValues.fanMode) @@ -400,7 +400,7 @@ export class ThermostatFanModeCCReport extends ThermostatFanModeCC { @ccValue(ThermostatFanModeCCValues.turnedOff) public readonly off: boolean | undefined; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { mode: getEnumMemberName(ThermostatFanMode, this.mode), }; @@ -408,7 +408,7 @@ export class ThermostatFanModeCCReport extends ThermostatFanModeCC { message.off = this.off; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -421,22 +421,21 @@ export class ThermostatFanModeCCGet extends ThermostatFanModeCC {} @CCCommand(ThermostatFanModeCommand.SupportedReport) export class ThermostatFanModeCCSupportedReport extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); this.supportedModes = parseBitMask( this.payload, ThermostatFanMode["Auto low"], ); } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Remember which fan modes are supported const fanModeValue = ThermostatFanModeCCValues.fanMode; - this.setMetadata(applHost, fanModeValue, { + this.setMetadata(ctx, fanModeValue, { ...fanModeValue.meta, states: enumValuesToMetadataStates( ThermostatFanMode, @@ -450,9 +449,9 @@ export class ThermostatFanModeCCSupportedReport extends ThermostatFanModeCC { @ccValue(ThermostatFanModeCCValues.supportedFanModes) public readonly supportedModes: ThermostatFanMode[]; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported modes": this.supportedModes .map( diff --git a/packages/cc/src/cc/ThermostatFanStateCC.ts b/packages/cc/src/cc/ThermostatFanStateCC.ts index e3df7bbe73e3..51edac9af02f 100644 --- a/packages/cc/src/cc/ThermostatFanStateCC.ts +++ b/packages/cc/src/cc/ThermostatFanStateCC.ts @@ -8,11 +8,7 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -23,6 +19,8 @@ import { import { CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -81,11 +79,11 @@ export class ThermostatFanStateCCAPI extends CCAPI { ThermostatFanStateCommand.Get, ); - const cc = new ThermostatFanStateCCGet(this.applHost, { + const cc = new ThermostatFanStateCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanStateCCReport >( cc, @@ -103,41 +101,45 @@ export class ThermostatFanStateCCAPI extends CCAPI { export class ThermostatFanStateCC extends CommandClass { declare ccCommand: ThermostatFanStateCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Fan State"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current thermostat fan state...", direction: "outbound", }); const currentStatus = await api.get(); if (currentStatus) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "received current thermostat fan state: " + getEnumMemberName(ThermostatFanState, currentStatus), @@ -150,10 +152,9 @@ export class ThermostatFanStateCC extends CommandClass { @CCCommand(ThermostatFanStateCommand.Report) export class ThermostatFanStateCCReport extends ThermostatFanStateCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length == 1); this.state = this.payload[0] & 0b1111; @@ -162,12 +163,12 @@ export class ThermostatFanStateCCReport extends ThermostatFanStateCC { @ccValue(ThermostatFanStateCCValues.fanState) public readonly state: ThermostatFanState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { state: getEnumMemberName(ThermostatFanState, this.state), }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/ThermostatModeCC.ts b/packages/cc/src/cc/ThermostatModeCC.ts index a045ae915be5..bea74df217b5 100644 --- a/packages/cc/src/cc/ThermostatModeCC.ts +++ b/packages/cc/src/cc/ThermostatModeCC.ts @@ -14,11 +14,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { buffer2hex, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -34,6 +30,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -131,11 +130,11 @@ export class ThermostatModeCCAPI extends CCAPI { ThermostatModeCommand.Get, ); - const cc = new ThermostatModeCCGet(this.applHost, { + const cc = new ThermostatModeCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatModeCCReport >( cc, @@ -181,13 +180,13 @@ export class ThermostatModeCCAPI extends CCAPI { manufacturerData = Buffer.from(manufacturerData, "hex"); } - const cc = new ThermostatModeCCSet(this.applHost, { + const cc = new ThermostatModeCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, mode, manufacturerData: manufacturerData as any, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupportedModes(): Promise< @@ -198,11 +197,11 @@ export class ThermostatModeCCAPI extends CCAPI { ThermostatModeCommand.SupportedGet, ); - const cc = new ThermostatModeCCSupportedGet(this.applHost, { + const cc = new ThermostatModeCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatModeCCSupportedReport >( cc, @@ -218,25 +217,27 @@ export class ThermostatModeCCAPI extends CCAPI { export class ThermostatModeCC extends CommandClass { declare ccCommand: ThermostatModeCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // First query the possible modes to set the metadata - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported thermostat modes...", direction: "outbound", @@ -251,13 +252,13 @@ export class ThermostatModeCC extends CommandClass { ) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported thermostat modes timed out, skipping interview...", @@ -266,32 +267,34 @@ export class ThermostatModeCC extends CommandClass { return; } - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Mode"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current status - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying current thermostat mode...", direction: "outbound", }); const currentStatus = await api.get(); if (currentStatus) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "received current thermostat mode: " + getEnumMemberName(ThermostatMode, currentStatus.mode), @@ -321,12 +324,11 @@ export type ThermostatModeCCSetOptions = @useSupervision() export class ThermostatModeCCSet extends ThermostatModeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatModeCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const manufacturerDataLength = (this.payload[0] >>> 5) & 0b111; @@ -351,7 +353,7 @@ export class ThermostatModeCCSet extends ThermostatModeCC { public mode: ThermostatMode; public manufacturerData?: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const manufacturerData = this.mode === ThermostatMode["Manufacturer specific"] && this.manufacturerData @@ -364,10 +366,10 @@ export class ThermostatModeCCSet extends ThermostatModeCC { ]), manufacturerData, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { mode: getEnumMemberName(ThermostatMode, this.mode), }; @@ -375,7 +377,7 @@ export class ThermostatModeCCSet extends ThermostatModeCC { message["manufacturer data"] = buffer2hex(this.manufacturerData); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -401,29 +403,26 @@ export type ThermostatModeCCReportOptions = @CCCommand(ThermostatModeCommand.Report) export class ThermostatModeCCReport extends ThermostatModeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatModeCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.mode = this.payload[0] & 0b11111; - if (this.version >= 3) { - const manufacturerDataLength = this.payload[0] >>> 5; - + // V3+ + const manufacturerDataLength = this.payload[0] >>> 5; + if (manufacturerDataLength > 0) { validatePayload( this.payload.length >= 1 + manufacturerDataLength, ); - if (manufacturerDataLength) { - this.manufacturerData = this.payload.subarray( - 1, - 1 + manufacturerDataLength, - ); - } + this.manufacturerData = this.payload.subarray( + 1, + 1 + manufacturerDataLength, + ); } } else { this.mode = options.mode; @@ -431,8 +430,8 @@ export class ThermostatModeCCReport extends ThermostatModeCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Update the supported modes if a mode is used that wasn't previously // reported to be supported. This shouldn't happen, but well... it does anyways @@ -440,7 +439,7 @@ export class ThermostatModeCCReport extends ThermostatModeCC { const supportedModesValue = ThermostatModeCCValues.supportedModes; const supportedModes = this.getValue( - applHost, + ctx, supportedModesValue, ); @@ -452,14 +451,14 @@ export class ThermostatModeCCReport extends ThermostatModeCC { supportedModes.push(this.mode); supportedModes.sort(); - this.setMetadata(applHost, thermostatModeValue, { + this.setMetadata(ctx, thermostatModeValue, { ...thermostatModeValue.meta, states: enumValuesToMetadataStates( ThermostatMode, supportedModes, ), }); - this.setValue(applHost, supportedModesValue, supportedModes); + this.setValue(ctx, supportedModesValue, supportedModes); } return true; } @@ -470,7 +469,7 @@ export class ThermostatModeCCReport extends ThermostatModeCC { @ccValue(ThermostatModeCCValues.manufacturerData) public readonly manufacturerData: Buffer | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const manufacturerDataLength = this.mode === ThermostatMode["Manufacturer specific"] && this.manufacturerData @@ -486,10 +485,10 @@ export class ThermostatModeCCReport extends ThermostatModeCC { manufacturerDataLength, ); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { mode: getEnumMemberName(ThermostatMode, this.mode), }; @@ -497,7 +496,7 @@ export class ThermostatModeCCReport extends ThermostatModeCC { message["manufacturer data"] = buffer2hex(this.manufacturerData); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -517,12 +516,11 @@ export interface ThermostatModeCCSupportedReportOptions @CCCommand(ThermostatModeCommand.SupportedReport) export class ThermostatModeCCSupportedReport extends ThermostatModeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatModeCCSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.supportedModes = parseBitMask( this.payload, @@ -533,12 +531,12 @@ export class ThermostatModeCCSupportedReport extends ThermostatModeCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Use this information to create the metadata for the mode property const thermostatModeValue = ThermostatModeCCValues.thermostatMode; - this.setMetadata(applHost, thermostatModeValue, { + this.setMetadata(ctx, thermostatModeValue, { ...thermostatModeValue.meta, states: enumValuesToMetadataStates( ThermostatMode, @@ -552,18 +550,18 @@ export class ThermostatModeCCSupportedReport extends ThermostatModeCC { @ccValue(ThermostatModeCCValues.supportedModes) public readonly supportedModes: ThermostatMode[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeBitMask( this.supportedModes, ThermostatMode["Manufacturer specific"], ThermostatMode.Off, ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported modes": this.supportedModes .map( diff --git a/packages/cc/src/cc/ThermostatOperatingStateCC.ts b/packages/cc/src/cc/ThermostatOperatingStateCC.ts index e593cbb502c1..e863afe1d701 100644 --- a/packages/cc/src/cc/ThermostatOperatingStateCC.ts +++ b/packages/cc/src/cc/ThermostatOperatingStateCC.ts @@ -7,11 +7,7 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -23,6 +19,8 @@ import { import { CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -87,11 +85,11 @@ export class ThermostatOperatingStateCCAPI extends PhysicalCCAPI { ThermostatOperatingStateCommand.Get, ); - const cc = new ThermostatOperatingStateCCGet(this.applHost, { + const cc = new ThermostatOperatingStateCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatOperatingStateCCReport >( cc, @@ -107,34 +105,38 @@ export class ThermostatOperatingStateCCAPI extends PhysicalCCAPI { export class ThermostatOperatingStateCC extends CommandClass { declare ccCommand: ThermostatOperatingStateCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Operating State"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the current state - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying thermostat operating state...", direction: "outbound", @@ -142,7 +144,7 @@ export class ThermostatOperatingStateCC extends CommandClass { const state = await api.get(); if (state) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `received current thermostat operating state: ${ getEnumMemberName( @@ -161,10 +163,9 @@ export class ThermostatOperatingStateCCReport extends ThermostatOperatingStateCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); this.state = this.payload[0]; @@ -173,9 +174,9 @@ export class ThermostatOperatingStateCCReport @ccValue(ThermostatOperatingStateCCValues.operatingState) public readonly state: ThermostatOperatingState; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { state: getEnumMemberName(ThermostatOperatingState, this.state), }, diff --git a/packages/cc/src/cc/ThermostatSetbackCC.ts b/packages/cc/src/cc/ThermostatSetbackCC.ts index 244049055f49..788ffe7dc6c3 100644 --- a/packages/cc/src/cc/ThermostatSetbackCC.ts +++ b/packages/cc/src/cc/ThermostatSetbackCC.ts @@ -1,18 +1,12 @@ -import type { - MessageOrCCLogEntry, - SupervisionResult, -} from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, + type MessageOrCCLogEntry, MessagePriority, + type SupervisionResult, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -25,6 +19,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -79,11 +75,11 @@ export class ThermostatSetbackCCAPI extends CCAPI { ThermostatSetbackCommand.Get, ); - const cc = new ThermostatSetbackCCGet(this.applHost, { + const cc = new ThermostatSetbackCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetbackCCReport >( cc, @@ -104,13 +100,13 @@ export class ThermostatSetbackCCAPI extends CCAPI { ThermostatSetbackCommand.Get, ); - const cc = new ThermostatSetbackCCSet(this.applHost, { + const cc = new ThermostatSetbackCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setbackType, setbackState, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -119,34 +115,38 @@ export class ThermostatSetbackCCAPI extends CCAPI { export class ThermostatSetbackCC extends CommandClass { declare ccCommand: ThermostatSetbackCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - await this.refreshValues(applHost); + await this.refreshValues(ctx); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Setback"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); // Query the thermostat state - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying the current thermostat state...", direction: "outbound", @@ -156,7 +156,7 @@ export class ThermostatSetbackCC extends CommandClass { const logMessage = `received current state: setback type: ${getEnumMemberName(SetbackType, setbackResp.setbackType)} setback state: ${setbackResp.setbackState}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -175,12 +175,11 @@ export interface ThermostatSetbackCCSetOptions extends CCCommandOptions { @useSupervision() export class ThermostatSetbackCCSet extends ThermostatSetbackCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetbackCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.setbackType = this.payload[0] & 0b11; @@ -198,17 +197,17 @@ export class ThermostatSetbackCCSet extends ThermostatSetbackCC { /** The offset from the setpoint in 0.1 Kelvin or a special mode */ public setbackState: SetbackState; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.setbackType & 0b11, encodeSetbackState(this.setbackState), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setback type": getEnumMemberName( SetbackType, @@ -231,12 +230,11 @@ export interface ThermostatSetbackCCReportOptions { @CCCommand(ThermostatSetbackCommand.Report) export class ThermostatSetbackCCReport extends ThermostatSetbackCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & ThermostatSetbackCCReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -255,17 +253,17 @@ export class ThermostatSetbackCCReport extends ThermostatSetbackCC { /** The offset from the setpoint in 0.1 Kelvin or a special mode */ public readonly setbackState: SetbackState; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.setbackType & 0b11, encodeSetbackState(this.setbackState), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setback type": getEnumMemberName( SetbackType, diff --git a/packages/cc/src/cc/ThermostatSetpointCC.ts b/packages/cc/src/cc/ThermostatSetpointCC.ts index 8e2bb4abf6d1..ee12fdf6559d 100644 --- a/packages/cc/src/cc/ThermostatSetpointCC.ts +++ b/packages/cc/src/cc/ThermostatSetpointCC.ts @@ -18,11 +18,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -38,6 +34,9 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -86,10 +85,6 @@ export const ThermostatSetpointCCValues = Object.freeze({ ...V.staticProperty("supportedSetpointTypes", undefined, { internal: true, }), - - ...V.staticProperty("setpointTypesInterpretation", undefined, { - internal: true, - }), }), ...V.defineDynamicCCValues(CommandClasses["Thermostat Setpoint"], { @@ -223,12 +218,12 @@ export class ThermostatSetpointCCAPI extends CCAPI { ThermostatSetpointCommand.Get, ); - const cc = new ThermostatSetpointCCGet(this.applHost, { + const cc = new ThermostatSetpointCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCReport >( cc, @@ -256,14 +251,14 @@ export class ThermostatSetpointCCAPI extends CCAPI { ThermostatSetpointCommand.Set, ); - const cc = new ThermostatSetpointCCSet(this.applHost, { + const cc = new ThermostatSetpointCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, value, scale, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -274,12 +269,12 @@ export class ThermostatSetpointCCAPI extends CCAPI { ThermostatSetpointCommand.CapabilitiesGet, ); - const cc = new ThermostatSetpointCCCapabilitiesGet(this.applHost, { + const cc = new ThermostatSetpointCCCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCCapabilitiesReport >( cc, @@ -308,11 +303,11 @@ export class ThermostatSetpointCCAPI extends CCAPI { ThermostatSetpointCommand.SupportedGet, ); - const cc = new ThermostatSetpointCCSupportedGet(this.applHost, { + const cc = new ThermostatSetpointCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCSupportedReport >( cc, @@ -329,7 +324,7 @@ export class ThermostatSetpointCC extends CommandClass { declare ccCommand: ThermostatSetpointCommand; public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -339,90 +334,55 @@ export class ThermostatSetpointCC extends CommandClass { propertyKey as any, ); } else { - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Setpoint"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (this.version <= 2) { - let setpointTypes: ThermostatSetpointType[]; - let interpretation: "A" | "B" | undefined; - // Whether our tests changed the assumed bitmask interpretation - let interpretationChanged = false; - - // Query the supported setpoint types - applHost.controllerLog.logNode(node.id, { - endpoint: this.endpointIndex, - message: "retrieving supported setpoint types...", - direction: "outbound", - }); - const resp = await api.getSupportedSetpointTypes(); - if (!resp) { - applHost.controllerLog.logNode(node.id, { - endpoint: this.endpointIndex, - message: - "Querying supported setpoint types timed out, skipping interview...", - level: "warn", - }); - return; - } - setpointTypes = [...resp]; - interpretation = undefined; // we don't know yet which interpretation the device uses - - // If necessary, test which interpretation the device follows - - // Assume interpretation B - // --> If setpoints 3,4,5 or 6 are supported, the assumption is wrong ==> A - function switchToInterpretationA(): void { - setpointTypes = setpointTypes.map( - (i) => thermostatSetpointTypeMap[i], - ); - interpretation = "A"; - interpretationChanged = true; - } - - if ([3, 4, 5, 6].some((type) => setpointTypes.includes(type))) { - applHost.controllerLog.logNode(node.id, { - endpoint: this.endpointIndex, - message: "uses Thermostat Setpoint bitmap interpretation A", - direction: "none", - }); - switchToInterpretationA(); - } else { - applHost.controllerLog.logNode(node.id, { - endpoint: this.endpointIndex, - message: - "Thermostat Setpoint bitmap interpretation is unknown, assuming B for now", - direction: "none", - }); - } + if (api.version <= 2) { + // It has been found that early implementations of this Command Class applied two non-interoperable + // interpretations of the bit mask advertising the support for specific Setpoint Types in the Thermostat + // Setpoint Supported Report Command. + // A controlling node SHOULD determine the supported Setpoint Types of a version 1 and version 2 + // supporting node by sending one Thermostat Setpoint Get Command at a time while incrementing + // the requested Setpoint Type. + // If the same Setpoint Type is advertised in the returned Thermostat Setpoint Report Command, the + // controlling node MUST conclude that the actual Setpoint Type is supported. + // If the Setpoint Type 0x00 (type N/A) is advertised in the returned Thermostat Setpoint Report + // Command, the controlling node MUST conclude that the actual Setpoint Type is not supported. // Now scan all endpoints. Each type we received a value for gets marked as supported const supportedSetpointTypes: ThermostatSetpointType[] = []; - for (let i = 0; i < setpointTypes.length; i++) { - const type = setpointTypes[i]; + for ( + let type: ThermostatSetpointType = + ThermostatSetpointType.Heating; + type <= ThermostatSetpointType["Full Power"]; + type++ + ) { const setpointName = getEnumMemberName( ThermostatSetpointType, type, ); // Every time, query the current value - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current value of setpoint ${setpointName}...`, @@ -440,59 +400,29 @@ export class ThermostatSetpointCC extends CommandClass { `received current value of setpoint ${setpointName}: ${setpoint.value} ${ setpoint.scale.unit ?? "" }`; - } else if (!interpretation) { - // The setpoint type is not supported, switch to interpretation A - applHost.controllerLog.logNode(node.id, { - endpoint: this.endpointIndex, - message: - `the setpoint type ${type} is unsupported, switching to interpretation A`, - direction: "none", - }); - switchToInterpretationA(); - // retry the current type and scan the remaining types as A - i--; - continue; } else { // We're sure about the interpretation - this should not happen - logMessage = `Setpoint ${setpointName} is not supported`; + logMessage = `setpoint ${setpointName} is not supported`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } - // If we made an assumption and did not switch to interpretation A, - // the device adheres to interpretation B - if (!interpretation && !interpretationChanged) { - // our assumption about interpretation B was correct - interpretation = "B"; - interpretationChanged = true; - } - - // Remember which setpoint types are actually supported, so we don't - // need to do this guesswork again + // Remember which setpoint types are actually supported this.setValue( - applHost, + ctx, ThermostatSetpointCCValues.supportedSetpointTypes, supportedSetpointTypes, ); - - // Also save the bitmap interpretation if we know it now - if (interpretationChanged) { - this.setValue( - applHost, - ThermostatSetpointCCValues.setpointTypesInterpretation, - interpretation, - ); - } } else { // Versions >= 3 adhere to bitmap interpretation A, so we can rely on getSupportedSetpointTypes // Query the supported setpoint types let setpointTypes: ThermostatSetpointType[] = []; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving supported setpoint types...", direction: "outbound", @@ -507,13 +437,13 @@ export class ThermostatSetpointCC extends CommandClass { ) .map((name) => `· ${name}`) .join("\n"); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying supported setpoint types timed out, skipping interview...", @@ -528,7 +458,7 @@ export class ThermostatSetpointCC extends CommandClass { type, ); // Find out the capabilities of this setpoint - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `retrieving capabilities for setpoint ${setpointName}...`, @@ -546,7 +476,7 @@ export class ThermostatSetpointCC extends CommandClass { `received capabilities for setpoint ${setpointName}: minimum value: ${setpointCaps.minValue} ${minValueUnit} maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -555,26 +485,28 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; } // Query the current value for all setpoint types - await this.refreshValues(applHost); + await this.refreshValues(ctx); } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Thermostat Setpoint"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const setpointTypes: ThermostatSetpointType[] = this.getValue( - applHost, + ctx, ThermostatSetpointCCValues.supportedSetpointTypes, ) ?? []; @@ -585,7 +517,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; type, ); // Every time, query the current value - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying current value of setpoint ${setpointName}...`, @@ -597,7 +529,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; `received current value of setpoint ${setpointName}: ${setpoint.value} ${ setpoint.scale.unit ?? "" }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -618,12 +550,11 @@ export interface ThermostatSetpointCCSetOptions extends CCCommandOptions { @useSupervision() export class ThermostatSetpointCCSet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.setpointType = this.payload[0] & 0b1111; @@ -644,21 +575,21 @@ export class ThermostatSetpointCCSet extends ThermostatSetpointCC { public value: number; public scale: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // If a config file overwrites how the float should be encoded, use that information - const override = this.host.getDeviceConfig?.(this.nodeId as number) + const override = ctx.getDeviceConfig?.(this.nodeId as number) ?.compat?.overrideFloatEncoding; this.payload = Buffer.concat([ Buffer.from([this.setpointType & 0b1111]), encodeFloatWithScale(this.value, this.scale, override), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const scale = getScale(this.scale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( ThermostatSetpointType, @@ -680,12 +611,11 @@ export interface ThermostatSetpointCCReportOptions extends CCCommandOptions { @CCCommand(ThermostatSetpointCommand.Report) export class ThermostatSetpointCCReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -710,29 +640,29 @@ export class ThermostatSetpointCCReport extends ThermostatSetpointCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; const scale = getScale(this.scale); const setpointValue = ThermostatSetpointCCValues.setpoint(this.type); const existingMetadata = this.getMetadata( - applHost, + ctx, setpointValue, ); // Update the metadata when it is missing or the unit has changed if (existingMetadata?.unit !== scale.unit) { - this.setMetadata(applHost, setpointValue, { + this.setMetadata(ctx, setpointValue, { ...(existingMetadata ?? setpointValue.meta), unit: scale.unit, }); } - this.setValue(applHost, setpointValue, this.value); + this.setValue(ctx, setpointValue, this.value); // Remember the device-preferred setpoint scale so it can be used in SET commands this.setValue( - applHost, + ctx, ThermostatSetpointCCValues.setpointScale(this.type), scale.key, ); @@ -743,18 +673,18 @@ export class ThermostatSetpointCCReport extends ThermostatSetpointCC { public scale: number; public value: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.type & 0b1111]), encodeFloatWithScale(this.value, this.scale), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const scale = getScale(this.scale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( ThermostatSetpointType, @@ -786,12 +716,11 @@ export interface ThermostatSetpointCCGetOptions extends CCCommandOptions { ) export class ThermostatSetpointCCGet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.setpointType = this.payload[0] & 0b1111; @@ -802,14 +731,14 @@ export class ThermostatSetpointCCGet extends ThermostatSetpointCC { public setpointType: ThermostatSetpointType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.setpointType & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( ThermostatSetpointType, @@ -836,12 +765,11 @@ export class ThermostatSetpointCCCapabilitiesReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCCapabilitiesReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -864,12 +792,12 @@ export class ThermostatSetpointCCCapabilitiesReport } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Predefine the metadata const setpointValue = ThermostatSetpointCCValues.setpoint(this.type); - this.setMetadata(applHost, setpointValue, { + this.setMetadata(ctx, setpointValue, { ...setpointValue.meta, min: this.minValue, max: this.maxValue, @@ -886,18 +814,18 @@ export class ThermostatSetpointCCCapabilitiesReport public minValueScale: number; public maxValueScale: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const min = encodeFloatWithScale(this.minValue, this.minValueScale); const max = encodeFloatWithScale(this.maxValue, this.maxValueScale); this.payload = Buffer.concat([Buffer.from([this.type]), min, max]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const minValueScale = getScale(this.minValueScale); const maxValueScale = getScale(this.maxValueScale); return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( ThermostatSetpointType, @@ -921,12 +849,11 @@ export interface ThermostatSetpointCCCapabilitiesGetOptions @expectedCCResponse(ThermostatSetpointCCCapabilitiesReport) export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCCapabilitiesGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.setpointType = this.payload[0] & 0b1111; @@ -937,14 +864,14 @@ export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { public setpointType: ThermostatSetpointType; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.setpointType & 0b1111]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "setpoint type": getEnumMemberName( ThermostatSetpointType, @@ -965,12 +892,11 @@ export interface ThermostatSetpointCCSupportedReportOptions @CCCommand(ThermostatSetpointCommand.SupportedReport) export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ThermostatSetpointCCSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -979,26 +905,11 @@ export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { bitMask, ThermostatSetpointType["N/A"], ); - if (this.version >= 3) { - // Interpretation A - this.supportedSetpointTypes = supported.map( - (i) => thermostatSetpointTypeMap[i], - ); - } else { - // It is unknown which interpretation the device complies to. - // This must be tested during the interview - this.supportedSetpointTypes = supported; - } - // TODO: - // Some devices skip the gaps in the ThermostatSetpointType (Interpretation A), some don't (Interpretation B) - // Devices with V3+ must comply with Interpretation A - // It is RECOMMENDED that a controlling node determines supported Setpoint Types - // by sending one Thermostat Setpoint Get Command at a time while incrementing - // the requested Setpoint Type. If the same Setpoint Type is advertised in the - // resulting Thermostat Setpoint Report Command, the controlling node MAY conclude - // that the actual Setpoint Type is supported. If the Setpoint Type 0x00 (type N/A) - // is advertised in the resulting Thermostat Setpoint Report Command, the controlling - // node MUST conclude that the actual Setpoint Type is not supported. + // We use this command only when we are sure that bitmask interpretation A is used + // FIXME: Figure out if we can do this without the CC version + this.supportedSetpointTypes = supported.map( + (i) => thermostatSetpointTypeMap[i], + ); } else { if (options.supportedSetpointTypes.length === 0) { throw new ZWaveError( @@ -1013,7 +924,7 @@ export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { @ccValue(ThermostatSetpointCCValues.supportedSetpointTypes) public readonly supportedSetpointTypes: readonly ThermostatSetpointType[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeBitMask( // Encode as interpretation A this.supportedSetpointTypes @@ -1022,12 +933,12 @@ export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { undefined, ThermostatSetpointType["N/A"], ); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported setpoint types": this.supportedSetpointTypes .map( diff --git a/packages/cc/src/cc/TimeCC.ts b/packages/cc/src/cc/TimeCC.ts index f6b3890413c5..5c72beb053e9 100644 --- a/packages/cc/src/cc/TimeCC.ts +++ b/packages/cc/src/cc/TimeCC.ts @@ -11,11 +11,7 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; @@ -24,6 +20,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -62,11 +59,11 @@ export class TimeCCAPI extends CCAPI { public async getTime() { this.assertSupportsCommand(TimeCommand, TimeCommand.TimeGet); - const cc = new TimeCCTimeGet(this.applHost, { + const cc = new TimeCCTimeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -83,25 +80,25 @@ export class TimeCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(TimeCommand, TimeCommand.TimeReport); - const cc = new TimeCCTimeReport(this.applHost, { + const cc = new TimeCCTimeReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, hour, minute, second, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public async getDate() { this.assertSupportsCommand(TimeCommand, TimeCommand.DateGet); - const cc = new TimeCCDateGet(this.applHost, { + const cc = new TimeCCDateGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -118,14 +115,14 @@ export class TimeCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(TimeCommand, TimeCommand.DateReport); - const cc = new TimeCCDateReport(this.applHost, { + const cc = new TimeCCDateReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, year, month, day, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -134,7 +131,7 @@ export class TimeCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(TimeCommand, TimeCommand.TimeOffsetSet); - const cc = new TimeCCTimeOffsetSet(this.applHost, { + const cc = new TimeCCTimeOffsetSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, standardOffset: timezone.standardOffset, @@ -142,17 +139,17 @@ export class TimeCCAPI extends CCAPI { dstStart: timezone.startDate, dstEnd: timezone.endDate, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getTimezone(): Promise> { this.assertSupportsCommand(TimeCommand, TimeCommand.TimeOffsetGet); - const cc = new TimeCCTimeOffsetGet(this.applHost, { + const cc = new TimeCCTimeOffsetGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< TimeCCTimeOffsetReport >( cc, @@ -174,7 +171,7 @@ export class TimeCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(TimeCommand, TimeCommand.TimeOffsetReport); - const cc = new TimeCCTimeOffsetReport(this.applHost, { + const cc = new TimeCCTimeOffsetReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, standardOffset: timezone.standardOffset, @@ -182,7 +179,7 @@ export class TimeCCAPI extends CCAPI { dstStart: timezone.startDate, dstEnd: timezone.endDate, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -191,18 +188,20 @@ export class TimeCCAPI extends CCAPI { export class TimeCC extends CommandClass { declare ccCommand: TimeCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses.Time, - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -210,7 +209,7 @@ export class TimeCC extends CommandClass { // Synchronize the slave's time if (api.version >= 2) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "setting timezone information...", direction: "outbound", @@ -221,7 +220,7 @@ export class TimeCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -235,10 +234,9 @@ export interface TimeCCTimeReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.TimeReport) export class TimeCCTimeReport extends TimeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | TimeCCTimeReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.hour = this.payload[0] & 0b11111; @@ -258,18 +256,18 @@ export class TimeCCTimeReport extends TimeCC { public minute: number; public second: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.hour & 0b11111, this.minute, this.second, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { time: `${padStart(this.hour.toString(), 2, "0")}:${ padStart( @@ -297,10 +295,9 @@ export interface TimeCCDateReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.DateReport) export class TimeCCDateReport extends TimeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | TimeCCDateReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.year = this.payload.readUInt16BE(0); @@ -317,7 +314,7 @@ export class TimeCCDateReport extends TimeCC { public month: number; public day: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ // 2 bytes placeholder for year 0, @@ -326,12 +323,12 @@ export class TimeCCDateReport extends TimeCC { this.day, ]); this.payload.writeUInt16BE(this.year, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { date: `${padStart(this.year.toString(), 4, "0")}-${ padStart( @@ -361,12 +358,11 @@ export interface TimeCCTimeOffsetSetOptions extends CCCommandOptions { @useSupervision() export class TimeCCTimeOffsetSet extends TimeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TimeCCTimeOffsetSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -386,7 +382,7 @@ export class TimeCCTimeOffsetSet extends TimeCC { public dstStartDate: Date; public dstEndDate: Date; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ encodeTimezone({ standardOffset: this.standardOffset, @@ -401,12 +397,12 @@ export class TimeCCTimeOffsetSet extends TimeCC { this.dstEndDate.getUTCHours(), ]), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "standard time offset": `${this.standardOffset} minutes`, "DST offset": `${this.dstOffset} minutes`, @@ -428,12 +424,11 @@ export interface TimeCCTimeOffsetReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.TimeOffsetReport) export class TimeCCTimeOffsetReport extends TimeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TimeCCTimeOffsetReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 9); const { standardOffset, dstOffset } = parseTimezone(this.payload); @@ -470,7 +465,7 @@ export class TimeCCTimeOffsetReport extends TimeCC { public dstStartDate: Date; public dstEndDate: Date; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ encodeTimezone({ standardOffset: this.standardOffset, @@ -485,12 +480,12 @@ export class TimeCCTimeOffsetReport extends TimeCC { this.dstEndDate.getUTCHours(), ]), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "standard time offset": `${this.standardOffset} minutes`, "DST offset": `${this.dstOffset} minutes`, diff --git a/packages/cc/src/cc/TimeParametersCC.ts b/packages/cc/src/cc/TimeParametersCC.ts index 496a6bf9f8f7..7d17582e4f6a 100644 --- a/packages/cc/src/cc/TimeParametersCC.ts +++ b/packages/cc/src/cc/TimeParametersCC.ts @@ -1,6 +1,5 @@ import { CommandClasses, - type IZWaveEndpoint, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, @@ -8,11 +7,16 @@ import { formatDate, validatePayload, } from "@zwave-js/core"; -import { type MaybeNotKnown } from "@zwave-js/core/safe"; +import { + type ControlsCC, + type EndpointId, + type MaybeNotKnown, + type SupportsCC, +} from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -28,6 +32,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -59,8 +65,8 @@ export const TimeParametersCCValues = Object.freeze({ * Determines if the node expects local time instead of UTC. */ function shouldUseLocalTime( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetDeviceConfig, + endpoint: EndpointId & SupportsCC & ControlsCC, ): boolean { // GH#311 Some nodes have no way to determine the time zone offset, // so they need to interpret the set time as local time instead of UTC. @@ -71,7 +77,7 @@ function shouldUseLocalTime( // Incidentally, this is also true when they don't support TimeCC at all // Use UTC though when the device config file explicitly requests it - const forceUTC = !!applHost.getDeviceConfig?.(endpoint.nodeId)?.compat + const forceUTC = !!ctx.getDeviceConfig?.(endpoint.nodeId)?.compat ?.useUTCInTimeParametersCC; if (forceUTC) return false; @@ -171,11 +177,11 @@ export class TimeParametersCCAPI extends CCAPI { TimeParametersCommand.Get, ); - const cc = new TimeParametersCCGet(this.applHost, { + const cc = new TimeParametersCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< TimeParametersCCReport >( cc, @@ -199,15 +205,15 @@ export class TimeParametersCCAPI extends CCAPI { )! : this.endpoint; - const useLocalTime = shouldUseLocalTime(this.applHost, endpointToCheck); + const useLocalTime = shouldUseLocalTime(this.host, endpointToCheck); - const cc = new TimeParametersCCSet(this.applHost, { + const cc = new TimeParametersCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, dateAndTime, useLocalTime, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -217,25 +223,27 @@ export class TimeParametersCCAPI extends CCAPI { export class TimeParametersCC extends CommandClass { declare ccCommand: TimeParametersCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Time Parameters"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Synchronize the node's time - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "setting current time...", direction: "outbound", @@ -243,17 +251,16 @@ export class TimeParametersCC extends CommandClass { await api.set(new Date()); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @CCCommand(TimeParametersCommand.Report) export class TimeParametersCCReport extends TimeParametersCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 7); const dateSegments = { year: this.payload.readUInt16BE(0), @@ -270,16 +277,16 @@ export class TimeParametersCCReport extends TimeParametersCC { ); } - public persistValues(applHost: ZWaveApplicationHost): boolean { + public persistValues(ctx: PersistValuesContext): boolean { // If necessary, fix the date and time before persisting it - const local = shouldUseLocalTime(applHost, this.getEndpoint(applHost)!); + const local = shouldUseLocalTime(ctx, this.getEndpoint(ctx)!); if (local) { // The initial assumption was incorrect, re-interpret the time const segments = dateToSegments(this.dateAndTime, false); this._dateAndTime = segmentsToDate(segments, local); } - return super.persistValues(applHost); + return super.persistValues(ctx); } private _dateAndTime: Date; @@ -288,9 +295,9 @@ export class TimeParametersCCReport extends TimeParametersCC { return this._dateAndTime; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "date and time": formatDate( this.dateAndTime, @@ -315,12 +322,11 @@ export interface TimeParametersCCSetOptions extends CCCommandOptions { @useSupervision() export class TimeParametersCCSet extends TimeParametersCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TimeParametersCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 7); const dateSegments = { @@ -349,24 +355,24 @@ export class TimeParametersCCSet extends TimeParametersCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { + public persistValues(ctx: PersistValuesContext): boolean { // We do not actually persist anything here, but we need access to the node // in order to interpret the date segments correctly - const local = shouldUseLocalTime(applHost, this.getEndpoint(applHost)!); + const local = shouldUseLocalTime(ctx, this.getEndpoint(ctx)!); if (local) { // The initial assumption was incorrect, re-interpret the time const segments = dateToSegments(this.dateAndTime, false); this.dateAndTime = segmentsToDate(segments, local); } - return super.persistValues(applHost); + return super.persistValues(ctx); } public dateAndTime: Date; private useLocalTime?: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const dateSegments = dateToSegments( this.dateAndTime, !!this.useLocalTime, @@ -382,12 +388,12 @@ export class TimeParametersCCSet extends TimeParametersCC { dateSegments.second, ]); this.payload.writeUInt16BE(dateSegments.year, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "date and time": formatDate( this.dateAndTime, diff --git a/packages/cc/src/cc/TransportServiceCC.ts b/packages/cc/src/cc/TransportServiceCC.ts index 15f7c766b1cc..fbabeca8c79d 100644 --- a/packages/cc/src/cc/TransportServiceCC.ts +++ b/packages/cc/src/cc/TransportServiceCC.ts @@ -8,9 +8,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { buffer2hex } from "@zwave-js/shared/safe"; import { @@ -80,10 +80,7 @@ export class TransportServiceCC extends CommandClass } /** Encapsulates a command that should be sent in multiple segments */ - public static encapsulate( - _host: ZWaveHost, - _cc: CommandClass, - ): TransportServiceCC { + public static encapsulate(_cc: CommandClass): TransportServiceCC { throw new Error("not implemented"); } } @@ -116,12 +113,11 @@ export function isTransportServiceEncapsulation( // @expectedCCResponse(TransportServiceCCReport) export class TransportServiceCCFirstSegment extends TransportServiceCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TransportServiceCCFirstSegmentOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Deserialization has already split the datagram size from the ccCommand. // Therefore we have one more payload byte @@ -171,7 +167,7 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { public partialDatagram: Buffer; public encapsulated!: CommandClass; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Transport Service re-uses the lower 3 bits of the ccCommand as payload this.ccCommand = (this.ccCommand & 0b11111_000) | ((this.datagramSize >>> 8) & 0b111); @@ -202,7 +198,7 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { // Write the checksum into the last two bytes of the payload this.payload.writeUInt16BE(crc, this.payload.length - 2); - return super.serialize(); + return super.serialize(ctx); } public expectMoreMessages(): boolean { @@ -225,9 +221,9 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { ); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "session ID": this.sessionId, "datagram size": this.datagramSize, @@ -249,12 +245,11 @@ export interface TransportServiceCCSubsequentSegmentOptions // @expectedCCResponse(TransportServiceCCReport) export class TransportServiceCCSubsequentSegment extends TransportServiceCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TransportServiceCCSubsequentSegmentOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // Deserialization has already split the datagram size from the ccCommand. // Therefore we have one more payload byte @@ -347,11 +342,11 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: [ TransportServiceCCFirstSegment, ...TransportServiceCCSubsequentSegment[], ], + ctx: CCParsingContext, ): void { // Concat the CC buffers const datagram = Buffer.allocUnsafe(this.datagramSize); @@ -370,14 +365,15 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { } // and deserialize the CC - this._encapsulated = CommandClass.from(this.host, { + this._encapsulated = CommandClass.from({ data: datagram, fromEncapsulation: true, encapCC: this, + context: ctx, }); } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { // Transport Service re-uses the lower 3 bits of the ccCommand as payload this.ccCommand = (this.ccCommand & 0b11111_000) | ((this.datagramSize >>> 8) & 0b111); @@ -411,7 +407,7 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { // Write the checksum into the last two bytes of the payload this.payload.writeUInt16BE(crc, this.payload.length - 2); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -425,9 +421,9 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { ); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "session ID": this.sessionId, "datagram size": this.datagramSize, @@ -467,12 +463,11 @@ function testResponseForSegmentRequest( @expectedCCResponse(TransportServiceCC, testResponseForSegmentRequest) export class TransportServiceCCSegmentRequest extends TransportServiceCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TransportServiceCCSegmentRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.sessionId = this.payload[1] >>> 4; @@ -487,18 +482,18 @@ export class TransportServiceCCSegmentRequest extends TransportServiceCC { public sessionId: number; public datagramOffset: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ ((this.sessionId & 0b1111) << 4) | ((this.datagramOffset >>> 8) & 0b111), this.datagramOffset & 0xff, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "session ID": this.sessionId, offset: this.datagramOffset, @@ -517,12 +512,11 @@ export interface TransportServiceCCSegmentCompleteOptions @CCCommand(TransportServiceCommand.SegmentComplete) export class TransportServiceCCSegmentComplete extends TransportServiceCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TransportServiceCCSegmentCompleteOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.sessionId = this.payload[1] >>> 4; @@ -533,14 +527,14 @@ export class TransportServiceCCSegmentComplete extends TransportServiceCC { public sessionId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([(this.sessionId & 0b1111) << 4]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "session ID": this.sessionId }, }; } @@ -554,12 +548,11 @@ export interface TransportServiceCCSegmentWaitOptions extends CCCommandOptions { @CCCommand(TransportServiceCommand.SegmentWait) export class TransportServiceCCSegmentWait extends TransportServiceCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | TransportServiceCCSegmentWaitOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.pendingSegments = this.payload[1]; @@ -570,14 +563,14 @@ export class TransportServiceCCSegmentWait extends TransportServiceCC { public pendingSegments: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.pendingSegments]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "pending segments": this.pendingSegments }, }; } diff --git a/packages/cc/src/cc/UserCodeCC.ts b/packages/cc/src/cc/UserCodeCC.ts index d264ffafe671..3569d7a22224 100644 --- a/packages/cc/src/cc/UserCodeCC.ts +++ b/packages/cc/src/cc/UserCodeCC.ts @@ -1,6 +1,6 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, @@ -16,9 +16,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, @@ -44,6 +44,10 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -184,16 +188,18 @@ function validateCode(code: string, supportedChars: string): boolean { function setUserCodeMetadata( this: UserCodeCC, - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetSupportedCCVersion, userId: number, userCode?: string | Buffer, ) { const statusValue = UserCodeCCValues.userIdStatus(userId); const codeValue = UserCodeCCValues.userCode(userId); + const ccVersion = getEffectiveCCVersion(ctx, this); + const supportedUserIDStatuses: UserIDStatus[] = - this.getValue(applHost, UserCodeCCValues.supportedUserIDStatuses) - ?? (this.version === 1 + this.getValue(ctx, UserCodeCCValues.supportedUserIDStatuses) + ?? (ccVersion === 1 ? [ UserIDStatus.Available, UserIDStatus.Enabled, @@ -207,7 +213,7 @@ function setUserCodeMetadata( UserIDStatus.PassageMode, ]); - this.ensureMetadata(applHost, statusValue, { + this.ensureMetadata(ctx, statusValue, { ...statusValue.meta, states: enumValuesToMetadataStates( UserIDStatus, @@ -223,14 +229,14 @@ function setUserCodeMetadata( maxLength: 10, label: `User Code (${userId})`, }; - if (this.getMetadata(applHost, codeValue)?.type !== codeMetadata.type) { - this.setMetadata(applHost, codeValue, codeMetadata); + if (this.getMetadata(ctx, codeValue)?.type !== codeMetadata.type) { + this.setMetadata(ctx, codeValue, codeMetadata); } } function persistUserCode( this: UserCodeCC, - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetSupportedCCVersion, userId: number, userIdStatus: UserIDStatus, userCode: string | Buffer, @@ -241,15 +247,15 @@ function persistUserCode( // Check if this code is supported if (userIdStatus === UserIDStatus.StatusNotAvailable) { // It is not, remove all values if any exist - this.removeValue(applHost, statusValue); - this.removeValue(applHost, codeValue); - this.removeMetadata(applHost, statusValue); - this.removeMetadata(applHost, codeValue); + this.removeValue(ctx, statusValue); + this.removeValue(ctx, codeValue); + this.removeMetadata(ctx, statusValue); + this.removeMetadata(ctx, codeValue); } else { // Always create metadata in case it does not exist - setUserCodeMetadata.call(this, applHost, userId, userCode); - this.setValue(applHost, statusValue, userIdStatus); - this.setValue(applHost, codeValue, userCode); + setUserCodeMetadata.call(this, ctx, userId, userCode); + this.setValue(ctx, statusValue, userIdStatus); + this.setValue(ctx, codeValue, userCode); } return true; @@ -444,11 +450,11 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.UsersNumberGet, ); - const cc = new UserCodeCCUsersNumberGet(this.applHost, { + const cc = new UserCodeCCUsersNumberGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCUsersNumberReport >( cc, @@ -477,13 +483,13 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.ExtendedUserCodeGet, ); - const cc = new UserCodeCCExtendedUserCodeGet(this.applHost, { + const cc = new UserCodeCCExtendedUserCodeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userId, reportMore: multiple, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCExtendedUserCodeReport >( cc, @@ -502,12 +508,12 @@ export class UserCodeCCAPI extends PhysicalCCAPI { } else { this.assertSupportsCommand(UserCodeCommand, UserCodeCommand.Get); - const cc = new UserCodeCCGet(this.applHost, { + const cc = new UserCodeCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userId, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -532,7 +538,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { this.assertSupportsCommand(UserCodeCommand, UserCodeCommand.Set); const numUsers = UserCodeCC.getSupportedUsersCached( - this.applHost, + this.host, this.endpoint, ); if (numUsers != undefined && userId > numUsers) { @@ -542,7 +548,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); } - const cc = new UserCodeCCSet(this.applHost, { + const cc = new UserCodeCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userId, @@ -550,7 +556,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { userCode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** Configures multiple user codes */ @@ -564,20 +570,20 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); const numUsers = UserCodeCC.getSupportedUsersCached( - this.applHost, + this.host, this.endpoint, ); const supportedStatuses = UserCodeCC.getSupportedUserIDStatusesCached( - this.applHost, + this.host, this.endpoint, ); const supportedASCIIChars = UserCodeCC.getSupportedASCIICharsCached( - this.applHost, + this.host, this.endpoint, ); const supportsMultipleUserCodeSet = UserCodeCC.supportsMultipleUserCodeSetCached( - this.applHost, + this.host, this.endpoint, ) ?? false; @@ -653,12 +659,12 @@ export class UserCodeCCAPI extends PhysicalCCAPI { } } } - const cc = new UserCodeCCExtendedUserCodeSet(this.applHost, { + const cc = new UserCodeCCExtendedUserCodeSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userCodes: codes, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -677,7 +683,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { this.assertSupportsCommand(UserCodeCommand, UserCodeCommand.Set); const numUsers = UserCodeCC.getSupportedUsersCached( - this.applHost, + this.host, this.endpoint, ); if (numUsers != undefined && userId > numUsers) { @@ -687,13 +693,13 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); } - const cc = new UserCodeCCSet(this.applHost, { + const cc = new UserCodeCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, userId, userIdStatus: UserIDStatus.Available, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -704,11 +710,11 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.CapabilitiesGet, ); - const cc = new UserCodeCCCapabilitiesGet(this.applHost, { + const cc = new UserCodeCCCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCCapabilitiesReport >( cc, @@ -734,11 +740,11 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.KeypadModeGet, ); - const cc = new UserCodeCCKeypadModeGet(this.applHost, { + const cc = new UserCodeCCKeypadModeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCKeypadModeReport >( cc, @@ -757,7 +763,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); const supportedModes = UserCodeCC.getSupportedKeypadModesCached( - this.applHost, + this.host, this.endpoint, ); @@ -778,13 +784,13 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); } - const cc = new UserCodeCCKeypadModeSet(this.applHost, { + const cc = new UserCodeCCKeypadModeSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, keypadMode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getAdminCode(): Promise> { @@ -793,11 +799,11 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.AdminCodeGet, ); - const cc = new UserCodeCCAdminCodeGet(this.applHost, { + const cc = new UserCodeCCAdminCodeGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCAdminCodeReport >( cc, @@ -816,7 +822,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); const supportedASCIIChars = UserCodeCC.getSupportedASCIICharsCached( - this.applHost, + this.host, this.endpoint, ); if (!supportedASCIIChars) { @@ -830,7 +836,7 @@ export class UserCodeCCAPI extends PhysicalCCAPI { if (!adminCode) { const supportsDeactivation = UserCodeCC .supportsAdminCodeDeactivationCached( - this.applHost, + this.host, this.endpoint, ); if (!supportsDeactivation) { @@ -846,13 +852,13 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); } - const cc = new UserCodeCCAdminCodeSet(this.applHost, { + const cc = new UserCodeCCAdminCodeSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, adminCode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getUserCodeChecksum(): Promise> { @@ -861,11 +867,11 @@ export class UserCodeCCAPI extends PhysicalCCAPI { UserCodeCommand.UserCodeChecksumGet, ); - const cc = new UserCodeCCUserCodeChecksumGet(this.applHost, { + const cc = new UserCodeCCUserCodeChecksumGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< UserCodeCCUserCodeChecksumReport >( cc, @@ -881,32 +887,34 @@ export class UserCodeCCAPI extends PhysicalCCAPI { export class UserCodeCC extends CommandClass { declare ccCommand: UserCodeCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["User Code"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); // Query capabilities first to determine what needs to be done when refreshing - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { message: "querying capabilities...", direction: "outbound", }); const caps = await api.getCapabilities(); if (!caps) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "User Code capabilities query timed out, skipping interview...", @@ -916,13 +924,13 @@ export class UserCodeCC extends CommandClass { } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying number of user codes...", direction: "outbound", }); const supportedUsers = await api.getUsersCount(); if (supportedUsers == undefined) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "Querying number of user codes timed out, skipping interview...", @@ -932,69 +940,71 @@ export class UserCodeCC extends CommandClass { } for (let userId = 1; userId <= supportedUsers; userId++) { - setUserCodeMetadata.call(this, applHost, userId); + setUserCodeMetadata.call(this, ctx, userId); } // Synchronize user codes and settings - if (applHost.options.interview?.queryAllUserCodes) { - await this.refreshValues(applHost); + if (ctx.getInterviewOptions()?.queryAllUserCodes) { + await this.refreshValues(ctx); } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["User Code"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); const supportsAdminCode: boolean = UserCodeCC.supportsAdminCodeCached( - applHost, + ctx, endpoint, ); const supportsUserCodeChecksum: boolean = this.getValue( - applHost, + ctx, UserCodeCCValues.supportsUserCodeChecksum, ) ?? false; const supportedKeypadModes: readonly KeypadMode[] = - this.getValue(applHost, UserCodeCCValues.supportedKeypadModes) + this.getValue(ctx, UserCodeCCValues.supportedKeypadModes) ?? []; const supportedUsers: number = - this.getValue(applHost, UserCodeCCValues.supportedUsers) ?? 0; + this.getValue(ctx, UserCodeCCValues.supportedUsers) ?? 0; const supportsMultipleUserCodeReport = !!this.getValue( - applHost, + ctx, UserCodeCCValues.supportsMultipleUserCodeReport, ); // Check for changed values and codes - if (this.version >= 2) { + if (api.version >= 2) { if (supportsAdminCode) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying admin code...", direction: "outbound", }); await api.getAdminCode(); } if (supportedKeypadModes.length > 1) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying active keypad mode...", direction: "outbound", }); await api.getKeypadMode(); } const storedUserCodeChecksum: number = - this.getValue(applHost, UserCodeCCValues.userCodeChecksum) ?? 0; + this.getValue(ctx, UserCodeCCValues.userCodeChecksum) ?? 0; let currentUserCodeChecksum: number | undefined = 0; if (supportsUserCodeChecksum) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "retrieving current user code checksum...", direction: "outbound", }); @@ -1004,7 +1014,7 @@ export class UserCodeCC extends CommandClass { !supportsUserCodeChecksum || currentUserCodeChecksum !== storedUserCodeChecksum ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "checksum changed or is not supported, querying all user codes...", direction: "outbound", @@ -1018,7 +1028,7 @@ export class UserCodeCC extends CommandClass { if (response) { nextUserId = response.nextUserId; } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Querying user code #${nextUserId} timed out, skipping the remaining interview...`, @@ -1036,7 +1046,7 @@ export class UserCodeCC extends CommandClass { } } else { // V1 - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying all user codes...", direction: "outbound", }); @@ -1051,10 +1061,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static getSupportedUsersCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue(UserCodeCCValues.supportedUsers.endpoint(endpoint.index)); } @@ -1064,10 +1074,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static getSupportedKeypadModesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.supportedKeypadModes.endpoint(endpoint.index), @@ -1079,10 +1089,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static getSupportedUserIDStatusesCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.supportedUserIDStatuses.endpoint( @@ -1096,10 +1106,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static getSupportedASCIICharsCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.supportedASCIIChars.endpoint(endpoint.index), @@ -1111,10 +1121,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static supportsAdminCodeCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost + const valueDB = ctx .getValueDB(endpoint.nodeId); return valueDB.getValue( UserCodeCCValues.supportsAdminCode.endpoint( @@ -1132,10 +1142,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static supportsAdminCodeDeactivationCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - const valueDB = applHost + const valueDB = ctx .getValueDB(endpoint.nodeId); return valueDB.getValue( UserCodeCCValues.supportsAdminCodeDeactivation.endpoint( @@ -1154,10 +1164,10 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the interview process */ public static supportsMultipleUserCodeSetCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, ): boolean { - return !!applHost + return !!ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.supportsMultipleUserCodeSet.endpoint( @@ -1171,11 +1181,11 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the user IDs have been queried. */ public static getUserIdStatusCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.userIdStatus(userId).endpoint(endpoint.index), @@ -1187,11 +1197,11 @@ export class UserCodeCC extends CommandClass { * This only works AFTER the user IDs have been queried. */ public static getUserCodeCached( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId, userId: number, ): MaybeNotKnown { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( UserCodeCCValues.userCode(userId).endpoint(endpoint.index), @@ -1224,12 +1234,11 @@ export type UserCodeCCSetOptions = @useSupervision() export class UserCodeCCSet extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & UserCodeCCSetOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userId = this.payload[0]; @@ -1283,19 +1292,19 @@ export class UserCodeCCSet extends UserCodeCC { public userIdStatus: UserIDStatus; public userCode: string | Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.userId, this.userIdStatus]), typeof this.userCode === "string" ? Buffer.from(this.userCode, "ascii") : this.userCode, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user id": this.userId, "id status": getEnumMemberName(UserIDStatus, this.userIdStatus), @@ -1317,10 +1326,9 @@ export class UserCodeCCReport extends UserCodeCC implements NotificationEventPayload { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | UserCodeCCReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); @@ -1348,10 +1356,7 @@ export class UserCodeCCReport extends UserCodeCC const userCodeString = userCodeBuffer.toString("utf8"); if (isPrintableASCII(userCodeString)) { this.userCode = userCodeString; - } else if ( - this.version === 1 - && isPrintableASCIIWithWhitespace(userCodeString) - ) { + } else if (isPrintableASCIIWithWhitespace(userCodeString)) { // Ignore leading and trailing whitespace in V1 reports if the rest is ASCII this.userCode = userCodeString.trim(); } else { @@ -1369,12 +1374,12 @@ export class UserCodeCCReport extends UserCodeCC public readonly userIdStatus: UserIDStatus; public readonly userCode: string | Buffer; - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; persistUserCode.call( this, - applHost, + ctx, this.userId, this.userIdStatus, this.userCode, @@ -1382,7 +1387,7 @@ export class UserCodeCCReport extends UserCodeCC return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { let userCodeBuffer: Buffer; if (typeof this.userCode === "string") { userCodeBuffer = Buffer.from(this.userCode, "ascii"); @@ -1394,12 +1399,12 @@ export class UserCodeCCReport extends UserCodeCC Buffer.from([this.userId, this.userIdStatus]), userCodeBuffer, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user id": this.userId, "id status": getEnumMemberName(UserIDStatus, this.userIdStatus), @@ -1423,10 +1428,9 @@ export interface UserCodeCCGetOptions extends CCCommandOptions { @expectedCCResponse(UserCodeCCReport) export class UserCodeCCGet extends UserCodeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | UserCodeCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.userId = this.payload[0]; @@ -1437,14 +1441,14 @@ export class UserCodeCCGet extends UserCodeCC { public userId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.userId]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user id": this.userId }, }; } @@ -1458,12 +1462,11 @@ export interface UserCodeCCUsersNumberReportOptions extends CCCommandOptions { @CCCommand(UserCodeCommand.UsersNumberReport) export class UserCodeCCUsersNumberReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCUsersNumberReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -1482,17 +1485,17 @@ export class UserCodeCCUsersNumberReport extends UserCodeCC { @ccValue(UserCodeCCValues.supportedUsers) public readonly supportedUsers: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(3); // If the node implements more than 255 users, this field MUST be set to 255 this.payload[0] = Math.min(255, this.supportedUsers); this.payload.writeUInt16BE(this.supportedUsers, 1); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported users": this.supportedUsers }, }; } @@ -1517,12 +1520,11 @@ export interface UserCodeCCCapabilitiesReportOptions extends CCCommandOptions { @CCCommand(UserCodeCommand.CapabilitiesReport) export class UserCodeCCCapabilitiesReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCCapabilitiesReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { let offset = 0; @@ -1617,7 +1619,7 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { @ccValue(UserCodeCCValues.supportedASCIIChars) public readonly supportedASCIIChars: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const supportedStatusesBitmask = encodeBitMask( this.supportedUserIDStatuses, undefined, @@ -1651,12 +1653,12 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { Buffer.from([controlByte3]), supportedKeysBitmask, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supports admin code": this.supportsAdminCode, "supports admin code deactivation": @@ -1694,12 +1696,11 @@ export interface UserCodeCCKeypadModeSetOptions extends CCCommandOptions { @useSupervision() export class UserCodeCCKeypadModeSet extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCKeypadModeSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.keypadMode = this.payload[0]; @@ -1710,14 +1711,14 @@ export class UserCodeCCKeypadModeSet extends UserCodeCC { public keypadMode: KeypadMode; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.keypadMode]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { mode: getEnumMemberName(KeypadMode, this.keypadMode) }, }; } @@ -1731,12 +1732,11 @@ export interface UserCodeCCKeypadModeReportOptions extends CCCommandOptions { @CCCommand(UserCodeCommand.KeypadModeReport) export class UserCodeCCKeypadModeReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCKeypadModeReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.keypadMode = this.payload[0]; @@ -1745,17 +1745,17 @@ export class UserCodeCCKeypadModeReport extends UserCodeCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; // Update the keypad modes metadata const supportedKeypadModes: KeypadMode[] = this.getValue( - applHost, + ctx, UserCodeCCValues.supportedKeypadModes, ) ?? [this.keypadMode]; const keypadModeValue = UserCodeCCValues.keypadMode; - this.setMetadata(applHost, keypadModeValue, { + this.setMetadata(ctx, keypadModeValue, { ...keypadModeValue.meta, states: enumValuesToMetadataStates( KeypadMode, @@ -1769,14 +1769,14 @@ export class UserCodeCCKeypadModeReport extends UserCodeCC { @ccValue(UserCodeCCValues.keypadMode) public readonly keypadMode: KeypadMode; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.keypadMode]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { keypadMode: getEnumMemberName(KeypadMode, this.keypadMode), }, @@ -1797,12 +1797,11 @@ export interface UserCodeCCAdminCodeSetOptions extends CCCommandOptions { @useSupervision() export class UserCodeCCAdminCodeSet extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCAdminCodeSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const codeLength = this.payload[0] & 0b1111; @@ -1817,17 +1816,17 @@ export class UserCodeCCAdminCodeSet extends UserCodeCC { public adminCode: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.adminCode.length & 0b1111]), Buffer.from(this.adminCode, "ascii"), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "admin code": userCodeToLogString(this.adminCode) }, }; } @@ -1841,12 +1840,11 @@ export interface UserCodeCCAdminCodeReportOptions extends CCCommandOptions { @CCCommand(UserCodeCommand.AdminCodeReport) export class UserCodeCCAdminCodeReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCAdminCodeReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const codeLength = this.payload[0] & 0b1111; @@ -1862,17 +1860,17 @@ export class UserCodeCCAdminCodeReport extends UserCodeCC { @ccValue(UserCodeCCValues.adminCode) public readonly adminCode: string; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.adminCode.length & 0b1111]), Buffer.from(this.adminCode, "ascii"), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "admin code": userCodeToLogString(this.adminCode) }, }; } @@ -1892,12 +1890,11 @@ export interface UserCodeCCUserCodeChecksumReportOptions @CCCommand(UserCodeCommand.UserCodeChecksumReport) export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCUserCodeChecksumReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.userCodeChecksum = this.payload.readUInt16BE(0); @@ -1909,15 +1906,15 @@ export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { @ccValue(UserCodeCCValues.userCodeChecksum) public readonly userCodeChecksum: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); this.payload.writeUInt16BE(this.userCodeChecksum, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user code checksum": num2hex(this.userCodeChecksum) }, }; } @@ -1942,12 +1939,11 @@ export interface UserCode { @useSupervision() export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCExtendedUserCodeSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -1961,7 +1957,7 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { public userCodes: UserCodeCCSetOptions[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const userCodeBuffers = this.userCodes.map((code) => { const ret = Buffer.concat([ Buffer.from([ @@ -1981,10 +1977,10 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { Buffer.from([this.userCodes.length]), ...userCodeBuffers, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; for (const { userId, userIdStatus, userCode } of this.userCodes) { message[`code #${userId}`] = `${ @@ -1994,7 +1990,7 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { } (status: ${getEnumMemberName(UserIDStatus, userIdStatus)})`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2003,10 +1999,9 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { @CCCommand(UserCodeCommand.ExtendedUserCodeReport) export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); const numCodes = this.payload[0]; let offset = 1; @@ -2025,13 +2020,13 @@ export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { this.nextUserId = this.payload.readUInt16BE(offset); } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; for (const { userId, userIdStatus, userCode } of this.userCodes) { persistUserCode.call( this, - applHost, + ctx, userId, userIdStatus, userCode, @@ -2043,7 +2038,7 @@ export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { public readonly userCodes: readonly UserCode[]; public readonly nextUserId: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; for (const { userId, userIdStatus, userCode } of this.userCodes) { message[`code #${userId}`] = `${ @@ -2054,7 +2049,7 @@ export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { } message["next user id"] = this.nextUserId; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2070,12 +2065,11 @@ export interface UserCodeCCExtendedUserCodeGetOptions extends CCCommandOptions { @expectedCCResponse(UserCodeCCExtendedUserCodeReport) export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | UserCodeCCExtendedUserCodeGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // TODO: Deserialize payload throw new ZWaveError( @@ -2091,15 +2085,15 @@ export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { public userId: number; public reportMore: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([0, 0, this.reportMore ? 1 : 0]); this.payload.writeUInt16BE(this.userId, 0); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "user id": this.userId, "report more": this.reportMore, diff --git a/packages/cc/src/cc/VersionCC.ts b/packages/cc/src/cc/VersionCC.ts index 6fd9152fce95..bda8e5f7b67e 100644 --- a/packages/cc/src/cc/VersionCC.ts +++ b/packages/cc/src/cc/VersionCC.ts @@ -15,11 +15,7 @@ import { securityClassOrder, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -27,6 +23,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -226,15 +223,8 @@ export class VersionCCAPI extends PhysicalCCAPI { case VersionCommand.CapabilitiesGet: case VersionCommand.CapabilitiesReport: case VersionCommand.ZWaveSoftwareReport: - // The API might have been created before the versions were determined, - // so `this.version` may contains a wrong value - return ( - this.applHost.getSafeCCVersion( - this.ccId, - this.endpoint.nodeId, - this.endpoint.index, - ) >= 3 - ); + return this.version >= 3; + case VersionCommand.ZWaveSoftwareGet: { return this.getValueDB().getValue( VersionCCValues.supportsZWaveSoftwareGet.endpoint( @@ -250,11 +240,11 @@ export class VersionCCAPI extends PhysicalCCAPI { public async get() { this.assertSupportsCommand(VersionCommand, VersionCommand.Get); - const cc = new VersionCCGet(this.applHost, { + const cc = new VersionCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -272,12 +262,12 @@ export class VersionCCAPI extends PhysicalCCAPI { public async sendReport(options: VersionCCReportOptions): Promise { this.assertSupportsCommand(VersionCommand, VersionCommand.Report); - const cc = new VersionCCReport(this.applHost, { + const cc = new VersionCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -289,12 +279,12 @@ export class VersionCCAPI extends PhysicalCCAPI { VersionCommand.CommandClassGet, ); - const cc = new VersionCCCommandClassGet(this.applHost, { + const cc = new VersionCCCommandClassGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, requestedCC, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCCommandClassReport >( cc, @@ -330,13 +320,13 @@ export class VersionCCAPI extends PhysicalCCAPI { break; } - const cc = new VersionCCCommandClassReport(this.applHost, { + const cc = new VersionCCCommandClassReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, requestedCC, ccVersion, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -346,11 +336,11 @@ export class VersionCCAPI extends PhysicalCCAPI { VersionCommand.CapabilitiesGet, ); - const cc = new VersionCCCapabilitiesGet(this.applHost, { + const cc = new VersionCCCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCCapabilitiesReport >( cc, @@ -367,13 +357,13 @@ export class VersionCCAPI extends PhysicalCCAPI { VersionCommand.CapabilitiesReport, ); - const cc = new VersionCCCapabilitiesReport(this.applHost, { + const cc = new VersionCCCapabilitiesReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, // At this time, we do not support responding to Z-Wave Software Get supportsZWaveSoftwareGet: false, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -383,11 +373,11 @@ export class VersionCCAPI extends PhysicalCCAPI { VersionCommand.ZWaveSoftwareGet, ); - const cc = new VersionCCZWaveSoftwareGet(this.applHost, { + const cc = new VersionCCZWaveSoftwareGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCZWaveSoftwareReport >( cc, @@ -420,8 +410,10 @@ export class VersionCC extends CommandClass { return [CommandClasses["Manufacturer Specific"]]; } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; // SDS13782: In a Multi Channel device, the Version Command Class MUST be supported by the Root Device, while // the Version Command Class SHOULD NOT be supported by individual End Points. @@ -431,18 +423,18 @@ export class VersionCC extends CommandClass { // implemented by the Multi Channel device; also in cases where the actual Command Class is only // provided by an End Point. - const endpoint = this.getEndpoint(applHost)!; + const endpoint = this.getEndpoint(ctx)!; // Use the CC API of the root device for all queries const api = CCAPI.create( CommandClasses.Version, - applHost, + ctx, node, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", @@ -453,7 +445,7 @@ export class VersionCC extends CommandClass { // but there are Z-Wave certification tests that require us to query all CCs const maxImplemented = getImplementedVersion(cc); if (maxImplemented === 0) { - applHost.controllerLog.logNode( + ctx.logNode( node.id, ` skipping query for ${CommandClasses[cc]} (${ num2hex( @@ -464,7 +456,7 @@ export class VersionCC extends CommandClass { return; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: ` querying the CC version for ${getCCName(cc)}...`, direction: "outbound", @@ -528,12 +520,12 @@ export class VersionCC extends CommandClass { } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `CC version query for ${ getCCName( @@ -553,15 +545,9 @@ export class VersionCC extends CommandClass { if (this.endpointIndex === 0) { // Step 1: Query Version CC version await queryCCVersion(CommandClasses.Version); - // The CC instance was created before the versions were determined, so `this.version` contains a wrong value - this.version = applHost.getSafeCCVersion( - CommandClasses.Version, - node.id, - this.endpointIndex, - ); // Step 2: Query node versions - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying node versions...", direction: "outbound", @@ -578,7 +564,7 @@ export class VersionCC extends CommandClass { logMessage += `\n hardware version: ${versionGetResponse.hardwareVersion}`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -587,7 +573,7 @@ export class VersionCC extends CommandClass { } // Step 3: Query all other CC versions - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying CC versions...", direction: "outbound", @@ -611,9 +597,9 @@ export class VersionCC extends CommandClass { } // Step 4: Query VersionCC capabilities (root device only) - if (this.endpointIndex === 0 && this.version >= 3) { + if (this.endpointIndex === 0 && api.version >= 3) { // Step 4a: Support for SoftwareGet - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying if Z-Wave Software Get is supported...", direction: "outbound", @@ -621,7 +607,7 @@ export class VersionCC extends CommandClass { const capsResponse = await api.getCapabilities(); if (capsResponse) { const { supportsZWaveSoftwareGet } = capsResponse; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Z-Wave Software Get is${ supportsZWaveSoftwareGet ? "" : " not" @@ -631,13 +617,13 @@ export class VersionCC extends CommandClass { if (supportsZWaveSoftwareGet) { // Step 4b: Query Z-Wave Software versions - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying Z-Wave software versions...", direction: "outbound", }); await api.getZWaveSoftware(); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "received Z-Wave software versions", direction: "inbound", @@ -647,7 +633,7 @@ export class VersionCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -662,19 +648,19 @@ export interface VersionCCReportOptions { @CCCommand(VersionCommand.Report) export class VersionCCReport extends VersionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (VersionCCReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 5); this.libraryType = this.payload[0]; this.protocolVersion = `${this.payload[1]}.${this.payload[2]}`; this.firmwareVersions = [`${this.payload[3]}.${this.payload[4]}`]; - if (this.version >= 2 && this.payload.length >= 7) { + if (this.payload.length >= 7) { + // V2+ this.hardwareVersion = this.payload[5]; const additionalFirmwares = this.payload[6]; validatePayload( @@ -727,7 +713,7 @@ export class VersionCCReport extends VersionCC { @ccValue(VersionCCValues.hardwareVersion) public readonly hardwareVersion: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.libraryType, ...this.protocolVersion @@ -756,10 +742,10 @@ export class VersionCCReport extends VersionCC { this.payload = Buffer.concat([this.payload, firmwaresBuffer]); } - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "library type": getEnumMemberName( ZWaveLibraryTypes, @@ -772,7 +758,7 @@ export class VersionCCReport extends VersionCC { message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -791,12 +777,11 @@ export interface VersionCCCommandClassReportOptions extends CCCommandOptions { @CCCommand(VersionCommand.CommandClassReport) export class VersionCCCommandClassReport extends VersionCC { public constructor( - host: ZWaveHost, options: | VersionCCCommandClassReportOptions | CommandClassDeserializationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.requestedCC = this.payload[0]; @@ -810,14 +795,14 @@ export class VersionCCCommandClassReport extends VersionCC { public ccVersion: number; public requestedCC: CommandClasses; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.requestedCC, this.ccVersion]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { CC: getCCName(this.requestedCC), version: this.ccVersion, @@ -846,12 +831,11 @@ function testResponseForVersionCommandClassGet( ) export class VersionCCCommandClassGet extends VersionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | VersionCCCommandClassGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.requestedCC = this.payload[0]; @@ -862,14 +846,14 @@ export class VersionCCCommandClassGet extends VersionCC { public requestedCC: CommandClasses; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.requestedCC]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { CC: getCCName(this.requestedCC) }, }; } @@ -883,12 +867,11 @@ export interface VersionCCCapabilitiesReportOptions { @CCCommand(VersionCommand.CapabilitiesReport) export class VersionCCCapabilitiesReport extends VersionCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (VersionCCCapabilitiesReportOptions & CCCommandOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -902,16 +885,16 @@ export class VersionCCCapabilitiesReport extends VersionCC { @ccValue(VersionCCValues.supportsZWaveSoftwareGet) public supportsZWaveSoftwareGet: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ (this.supportsZWaveSoftwareGet ? 0b100 : 0) | 0b11, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supports Z-Wave Software Get command": this.supportsZWaveSoftwareGet, @@ -927,10 +910,9 @@ export class VersionCCCapabilitiesGet extends VersionCC {} @CCCommand(VersionCommand.ZWaveSoftwareReport) export class VersionCCZWaveSoftwareReport extends VersionCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 23); this.sdkVersion = parseVersion(this.payload); @@ -989,7 +971,7 @@ export class VersionCCZWaveSoftwareReport extends VersionCC { @ccValue(VersionCCValues.applicationBuildNumber) public readonly applicationBuildNumber: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { "SDK version": this.sdkVersion, }; @@ -1014,7 +996,7 @@ export class VersionCCZWaveSoftwareReport extends VersionCC { message["application build number"] = this.applicationBuildNumber; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } diff --git a/packages/cc/src/cc/WakeUpCC.ts b/packages/cc/src/cc/WakeUpCC.ts index a09cd31467a7..1fe8a52c93c3 100644 --- a/packages/cc/src/cc/WakeUpCC.ts +++ b/packages/cc/src/cc/WakeUpCC.ts @@ -9,11 +9,7 @@ import { supervisedCommandSucceeded, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { clamp } from "alcalzone-shared/math"; @@ -30,6 +26,8 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -107,7 +105,7 @@ export class WakeUpCCAPI extends CCAPI { } const result = await this.setInterval( value, - this.applHost.ownNodeId ?? 1, + this.host.ownNodeId ?? 1, ); // Verify the change after a short delay, unless the command was supervised and successful @@ -134,11 +132,11 @@ export class WakeUpCCAPI extends CCAPI { public async getInterval() { this.assertSupportsCommand(WakeUpCommand, WakeUpCommand.IntervalGet); - const cc = new WakeUpCCIntervalGet(this.applHost, { + const cc = new WakeUpCCIntervalGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WakeUpCCIntervalReport >( cc, @@ -156,11 +154,11 @@ export class WakeUpCCAPI extends CCAPI { WakeUpCommand.IntervalCapabilitiesGet, ); - const cc = new WakeUpCCIntervalCapabilitiesGet(this.applHost, { + const cc = new WakeUpCCIntervalCapabilitiesGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WakeUpCCIntervalCapabilitiesReport >( cc, @@ -184,13 +182,13 @@ export class WakeUpCCAPI extends CCAPI { ): Promise { this.assertSupportsCommand(WakeUpCommand, WakeUpCommand.IntervalSet); - const cc = new WakeUpCCIntervalSet(this.applHost, { + const cc = new WakeUpCCIntervalSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, wakeUpInterval, controllerNodeId, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async sendNoMoreInformation(): Promise { @@ -199,11 +197,11 @@ export class WakeUpCCAPI extends CCAPI { WakeUpCommand.NoMoreInformation, ); - const cc = new WakeUpCCNoMoreInformation(this.applHost, { + const cc = new WakeUpCCNoMoreInformation({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - await this.applHost.sendCommand(cc, { + await this.host.sendCommand(cc, { ...this.commandOptions, // This command must be sent as part of the wake up queue priority: MessagePriority.WakeUp, @@ -222,30 +220,32 @@ export class WakeUpCCAPI extends CCAPI { export class WakeUpCC extends CommandClass { declare ccCommand: WakeUpCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Wake Up"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - if (applHost.isControllerNode(node.id)) { - applHost.controllerLog.logNode( + if (node.id === ctx.ownNodeId) { + ctx.logNode( node.id, `skipping wakeup configuration for the controller`, ); } else if (node.isFrequentListening) { - applHost.controllerLog.logNode( + ctx.logNode( node.id, `skipping wakeup configuration for frequent listening device`, ); @@ -256,8 +256,8 @@ export class WakeUpCC extends CommandClass { let maxInterval: number | undefined; // Retrieve the allowed wake up intervals and wake on demand support if possible - if (this.version >= 2) { - applHost.controllerLog.logNode(node.id, { + if (api.version >= 2) { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving wakeup capabilities from the device...", @@ -271,7 +271,7 @@ minimum wakeup interval: ${wakeupCaps.minWakeUpInterval} seconds maximum wakeup interval: ${wakeupCaps.maxWakeUpInterval} seconds wakeup interval steps: ${wakeupCaps.wakeUpIntervalSteps} seconds wakeup on demand supported: ${wakeupCaps.wakeUpOnDemandSupported}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -285,7 +285,7 @@ wakeup on demand supported: ${wakeupCaps.wakeUpOnDemandSupported}`; // We have no intention of changing the interval (maybe some time in the future) // So for now get the current interval and just set the controller ID - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "retrieving wakeup interval from the device...", direction: "outbound", @@ -296,7 +296,7 @@ wakeup on demand supported: ${wakeupCaps.wakeUpOnDemandSupported}`; const logMessage = `received wakeup configuration: wakeup interval: ${wakeupResp.wakeUpInterval} seconds controller node: ${wakeupResp.controllerNodeId}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -310,7 +310,7 @@ controller node: ${wakeupResp.controllerNodeId}`; currentControllerNodeId = 0; // assume not set } - const ownNodeId = applHost.ownNodeId; + const ownNodeId = ctx.ownNodeId; // Only change the destination if necessary if (currentControllerNodeId !== ownNodeId) { // Spec compliance: Limit the interval to the allowed range, but... @@ -327,18 +327,18 @@ controller node: ${wakeupResp.controllerNodeId}`; ); } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "configuring wakeup destination node", direction: "outbound", }); await api.setInterval(desiredInterval, ownNodeId); this.setValue( - applHost, + ctx, WakeUpCCValues.controllerNodeId, ownNodeId, ); - applHost.controllerLog.logNode( + ctx.logNode( node.id, "wakeup destination node changed!", ); @@ -346,7 +346,7 @@ controller node: ${wakeupResp.controllerNodeId}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -360,12 +360,11 @@ export interface WakeUpCCIntervalSetOptions extends CCCommandOptions { @useSupervision() export class WakeUpCCIntervalSet extends WakeUpCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WakeUpCCIntervalSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.wakeUpInterval = this.payload.readUIntBE(0, 3); @@ -379,7 +378,7 @@ export class WakeUpCCIntervalSet extends WakeUpCC { public wakeUpInterval: number; public controllerNodeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ 0, 0, @@ -387,12 +386,12 @@ export class WakeUpCCIntervalSet extends WakeUpCC { this.controllerNodeId, ]); this.payload.writeUIntBE(this.wakeUpInterval, 0, 3); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "wake-up interval": `${this.wakeUpInterval} seconds`, "controller node id": this.controllerNodeId, @@ -404,10 +403,9 @@ export class WakeUpCCIntervalSet extends WakeUpCC { @CCCommand(WakeUpCommand.IntervalReport) export class WakeUpCCIntervalReport extends WakeUpCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); this.wakeUpInterval = this.payload.readUIntBE(0, 3); @@ -420,9 +418,9 @@ export class WakeUpCCIntervalReport extends WakeUpCC { @ccValue(WakeUpCCValues.controllerNodeId) public readonly controllerNodeId: number; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "wake-up interval": `${this.wakeUpInterval} seconds`, "controller node id": this.controllerNodeId, @@ -444,10 +442,9 @@ export class WakeUpCCNoMoreInformation extends WakeUpCC {} @CCCommand(WakeUpCommand.IntervalCapabilitiesReport) export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 12); this.minWakeUpInterval = this.payload.readUIntBE(0, 3); @@ -455,17 +452,17 @@ export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { this.defaultWakeUpInterval = this.payload.readUIntBE(6, 3); this.wakeUpIntervalSteps = this.payload.readUIntBE(9, 3); - // Get 'Wake Up on Demand Support' if node supports V3 and sends 13th byte - if (this.version >= 3 && this.payload.length >= 13) { + if (this.payload.length >= 13) { + // V3+ this.wakeUpOnDemandSupported = !!(this.payload[12] & 0b1); } else { this.wakeUpOnDemandSupported = false; } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; - const valueDB = this.getValueDB(applHost); + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + const valueDB = this.getValueDB(ctx); // Store the received information as metadata for the wake up interval valueDB.setMetadata( @@ -496,9 +493,9 @@ export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { @ccValue(WakeUpCCValues.wakeUpOnDemandSupported) public readonly wakeUpOnDemandSupported: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "default interval": `${this.defaultWakeUpInterval} seconds`, "minimum interval": `${this.minWakeUpInterval} seconds`, diff --git a/packages/cc/src/cc/WindowCoveringCC.ts b/packages/cc/src/cc/WindowCoveringCC.ts index aafdaff85b75..85eba7531edd 100644 --- a/packages/cc/src/cc/WindowCoveringCC.ts +++ b/packages/cc/src/cc/WindowCoveringCC.ts @@ -11,11 +11,7 @@ import { validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -35,6 +31,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -418,7 +415,7 @@ export class WindowCoveringCCAPI extends CCAPI { ); // and optimistically update the currentValue for (const node of affectedNodes) { - this.applHost + this.host .tryGetValueDB(node.id) ?.setValue(currentValueValueId, value); } @@ -473,11 +470,11 @@ export class WindowCoveringCCAPI extends CCAPI { WindowCoveringCommand.SupportedGet, ); - const cc = new WindowCoveringCCSupportedGet(this.applHost, { + const cc = new WindowCoveringCCSupportedGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WindowCoveringCCSupportedReport >( cc, @@ -494,12 +491,12 @@ export class WindowCoveringCCAPI extends CCAPI { WindowCoveringCommand.Get, ); - const cc = new WindowCoveringCCGet(this.applHost, { + const cc = new WindowCoveringCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WindowCoveringCCReport >( cc, @@ -523,14 +520,14 @@ export class WindowCoveringCCAPI extends CCAPI { WindowCoveringCommand.StartLevelChange, ); - const cc = new WindowCoveringCCSet(this.applHost, { + const cc = new WindowCoveringCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, targetValues, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -544,7 +541,7 @@ export class WindowCoveringCCAPI extends CCAPI { WindowCoveringCommand.StartLevelChange, ); - const cc = new WindowCoveringCCStartLevelChange(this.applHost, { + const cc = new WindowCoveringCCStartLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, @@ -552,7 +549,7 @@ export class WindowCoveringCCAPI extends CCAPI { duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -564,13 +561,13 @@ export class WindowCoveringCCAPI extends CCAPI { WindowCoveringCommand.StopLevelChange, ); - const cc = new WindowCoveringCCStopLevelChange(this.applHost, { + const cc = new WindowCoveringCCStopLevelChange({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, parameter, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -580,24 +577,26 @@ export class WindowCoveringCCAPI extends CCAPI { export class WindowCoveringCC extends CommandClass { declare ccCommand: WindowCoveringCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Window Covering"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying supported window covering parameters...", direction: "outbound", @@ -612,7 +611,7 @@ ${ ) .join("\n") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -622,31 +621,31 @@ ${ for (const param of supported) { // Default values this.setMetadata( - applHost, + ctx, WindowCoveringCCValues.currentValue(param), ); this.setMetadata( - applHost, + ctx, WindowCoveringCCValues.targetValue(param), ); this.setMetadata( - applHost, + ctx, WindowCoveringCCValues.duration(param), ); // Level change values this.setMetadata( - applHost, + ctx, WindowCoveringCCValues.levelChangeUp(param), ); this.setMetadata( - applHost, + ctx, WindowCoveringCCValues.levelChangeDown(param), ); // And for the odd parameters (with position support), query the position if (param % 2 === 1) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `querying position for parameter ${ getEnumMemberName( @@ -662,18 +661,18 @@ ${ } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } public translatePropertyKey( - _applHost: ZWaveApplicationHost, - _property: string | number, + ctx: GetValueDB, + property: string | number, propertyKey: string | number, ): string | undefined { if (typeof propertyKey === "number") { return getEnumMemberName(WindowCoveringParameter, propertyKey); } - return super.translatePropertyKey(_applHost, _property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } @@ -687,12 +686,11 @@ export interface WindowCoveringCCSupportedReportOptions @CCCommand(WindowCoveringCommand.SupportedReport) export class WindowCoveringCCSupportedReport extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WindowCoveringCCSupportedReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); @@ -712,7 +710,7 @@ export class WindowCoveringCCSupportedReport extends WindowCoveringCC { @ccValue(WindowCoveringCCValues.supportedParameters) public readonly supportedParameters: readonly WindowCoveringParameter[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const bitmask = encodeBitMask( this.supportedParameters, undefined, @@ -725,12 +723,12 @@ export class WindowCoveringCCSupportedReport extends WindowCoveringCC { bitmask.subarray(0, numBitmaskBytes), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "supported parameters": this.supportedParameters .map( @@ -755,10 +753,9 @@ export class WindowCoveringCCSupportedGet extends WindowCoveringCC {} @CCCommand(WindowCoveringCommand.Report) export class WindowCoveringCCReport extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); this.parameter = this.payload[0]; this.currentValue = this.payload[1]; @@ -785,9 +782,9 @@ export class WindowCoveringCCReport extends WindowCoveringCC { ) public readonly duration: Duration; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { parameter: getEnumMemberName( WindowCoveringParameter, @@ -817,12 +814,11 @@ function testResponseForWindowCoveringGet( @expectedCCResponse(WindowCoveringCCReport, testResponseForWindowCoveringGet) export class WindowCoveringCCGet extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WindowCoveringCCGetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.parameter = this.payload[0]; @@ -833,14 +829,14 @@ export class WindowCoveringCCGet extends WindowCoveringCC { public parameter: WindowCoveringParameter; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.parameter]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { parameter: getEnumMemberName( WindowCoveringParameter, @@ -864,12 +860,11 @@ export interface WindowCoveringCCSetOptions extends CCCommandOptions { @useSupervision() export class WindowCoveringCCSet extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WindowCoveringCCSetOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const numEntries = this.payload[0] & 0b11111; @@ -900,7 +895,7 @@ export class WindowCoveringCCSet extends WindowCoveringCC { }[]; public duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const numEntries = this.targetValues.length & 0b11111; this.payload = Buffer.allocUnsafe(2 + numEntries * 2); @@ -915,10 +910,10 @@ export class WindowCoveringCCSet extends WindowCoveringCC { this.duration ?? Duration.default() ).serializeSet(); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; for (const { parameter, value } of this.targetValues) { message[getEnumMemberName(WindowCoveringParameter, parameter)] = @@ -928,7 +923,7 @@ export class WindowCoveringCCSet extends WindowCoveringCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -947,12 +942,11 @@ export interface WindowCoveringCCStartLevelChangeOptions @useSupervision() export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WindowCoveringCCStartLevelChangeOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.direction = !!(this.payload[0] & 0b0100_0000) ? "down" : "up"; @@ -971,16 +965,16 @@ export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { public direction: keyof typeof LevelChangeDirection; public duration: Duration | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.direction === "down" ? 0b0100_0000 : 0b0000_0000, this.parameter, (this.duration ?? Duration.default()).serializeSet(), ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { parameter: getEnumMemberName( WindowCoveringParameter, @@ -992,7 +986,7 @@ export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1009,12 +1003,11 @@ export interface WindowCoveringCCStopLevelChangeOptions @useSupervision() export class WindowCoveringCCStopLevelChange extends WindowCoveringCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | WindowCoveringCCStopLevelChangeOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.parameter = this.payload[0]; @@ -1025,14 +1018,14 @@ export class WindowCoveringCCStopLevelChange extends WindowCoveringCC { public parameter: WindowCoveringParameter; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.parameter]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { parameter: getEnumMemberName( WindowCoveringParameter, diff --git a/packages/cc/src/cc/ZWavePlusCC.ts b/packages/cc/src/cc/ZWavePlusCC.ts index 1716268b7440..71eb1db7a881 100644 --- a/packages/cc/src/cc/ZWavePlusCC.ts +++ b/packages/cc/src/cc/ZWavePlusCC.ts @@ -5,11 +5,7 @@ import { MessagePriority, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCEncodingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName, num2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; @@ -17,6 +13,7 @@ import { type CCCommandOptions, CommandClass, type CommandClassDeserializationOptions, + type InterviewContext, gotDeserializationOptions, } from "../lib/CommandClass"; import { @@ -83,11 +80,11 @@ export class ZWavePlusCCAPI extends PhysicalCCAPI { public async get() { this.assertSupportsCommand(ZWavePlusCommand, ZWavePlusCommand.Get); - const cc = new ZWavePlusCCGet(this.applHost, { + const cc = new ZWavePlusCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -106,12 +103,12 @@ export class ZWavePlusCCAPI extends PhysicalCCAPI { public async sendReport(options: ZWavePlusCCReportOptions): Promise { this.assertSupportsCommand(ZWavePlusCommand, ZWavePlusCommand.Report); - const cc = new ZWavePlusCCReport(this.applHost, { + const cc = new ZWavePlusCCReport({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -121,24 +118,26 @@ export class ZWavePlusCCAPI extends PhysicalCCAPI { export class ZWavePlusCC extends CommandClass { declare ccCommand: ZWavePlusCommand; - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; - const endpoint = this.getEndpoint(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; + const endpoint = this.getEndpoint(ctx)!; const api = CCAPI.create( CommandClasses["Z-Wave Plus Info"], - applHost, + ctx, endpoint, ).withOptions({ priority: MessagePriority.NodeQuery, }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing ${this.ccName}...`, direction: "none", }); - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: "querying Z-Wave+ information...", direction: "outbound", @@ -152,7 +151,7 @@ role type: ${ZWavePlusRoleType[zwavePlusResponse.roleType]} node type: ${ZWavePlusNodeType[zwavePlusResponse.nodeType]} installer icon: ${num2hex(zwavePlusResponse.installerIcon)} user icon: ${num2hex(zwavePlusResponse.userIcon)}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -160,7 +159,7 @@ user icon: ${num2hex(zwavePlusResponse.userIcon)}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -176,12 +175,11 @@ export interface ZWavePlusCCReportOptions { @CCCommand(ZWavePlusCommand.Report) export class ZWavePlusCCReport extends ZWavePlusCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | (CCCommandOptions & ZWavePlusCCReportOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 7); this.zwavePlusVersion = this.payload[0]; @@ -213,7 +211,7 @@ export class ZWavePlusCCReport extends ZWavePlusCC { @ccValue(ZWavePlusCCValues.userIcon) public userIcon: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.zwavePlusVersion, this.roleType, @@ -226,12 +224,12 @@ export class ZWavePlusCCReport extends ZWavePlusCC { ]); this.payload.writeUInt16BE(this.installerIcon, 3); this.payload.writeUInt16BE(this.userIcon, 5); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { version: this.zwavePlusVersion, "node type": getEnumMemberName( diff --git a/packages/cc/src/cc/ZWaveProtocolCC.ts b/packages/cc/src/cc/ZWaveProtocolCC.ts index 12d48da99957..cd45e039a9ae 100644 --- a/packages/cc/src/cc/ZWaveProtocolCC.ts +++ b/packages/cc/src/cc/ZWaveProtocolCC.ts @@ -20,7 +20,7 @@ import { parseNodeProtocolInfoAndDeviceClass, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; +import type { CCEncodingContext } from "@zwave-js/host"; import { type CCCommandOptions, CommandClass, @@ -78,12 +78,11 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC implements NodeInformationFrame { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNodeInformationFrameOptions, ) { - super(host, options); + super(options); let nif: NodeInformationFrame; if (gotDeserializationOptions(options)) { @@ -121,9 +120,9 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC public supportsBeaming: boolean; public supportedCCs: CommandClasses[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = encodeNodeInformationFrame(this); - return super.serialize(); + return super.serialize(ctx); } } @@ -142,12 +141,11 @@ export interface ZWaveProtocolCCAssignIDsOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.AssignIDs) export class ZWaveProtocolCCAssignIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCAssignIDsOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 5); this.assignedNodeId = this.payload[0]; @@ -161,11 +159,11 @@ export class ZWaveProtocolCCAssignIDs extends ZWaveProtocolCC { public assignedNodeId: number; public homeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(5); this.payload[0] = this.assignedNodeId; this.payload.writeUInt32BE(this.homeId, 1); - return super.serialize(); + return super.serialize(ctx); } } @@ -181,12 +179,11 @@ export interface ZWaveProtocolCCFindNodesInRangeOptions @CCCommand(ZWaveProtocolCommand.FindNodesInRange) export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCFindNodesInRangeOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const speedPresent = this.payload[0] & 0b1000_0000; @@ -227,7 +224,7 @@ export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { public wakeUpTime: WakeUpTime; public dataRate: ZWaveDataRate; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const nodesBitmask = encodeBitMask(this.candidateNodeIds, MAX_NODES); const speedAndLength = 0b1000_0000 | nodesBitmask.length; this.payload = Buffer.concat([ @@ -235,7 +232,7 @@ export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { nodesBitmask, Buffer.from([this.wakeUpTime, this.dataRate]), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -248,12 +245,11 @@ export interface ZWaveProtocolCCRangeInfoOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.RangeInfo) export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCRangeInfoOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const bitmaskLength = this.payload[0] & 0b0001_1111; @@ -276,7 +272,7 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { public neighborNodeIds: number[]; public wakeUpTime?: WakeUpTime; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); this.payload = Buffer.concat([ Buffer.from([nodesBitmask.length]), @@ -285,7 +281,7 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { ? Buffer.from([this.wakeUpTime]) : Buffer.alloc(0), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -303,12 +299,11 @@ export interface ZWaveProtocolCCCommandCompleteOptions @CCCommand(ZWaveProtocolCommand.CommandComplete) export class ZWaveProtocolCCCommandComplete extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCCommandCompleteOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.sequenceNumber = this.payload[0]; @@ -319,9 +314,9 @@ export class ZWaveProtocolCCCommandComplete extends ZWaveProtocolCC { public sequenceNumber: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sequenceNumber]); - return super.serialize(); + return super.serialize(ctx); } } @@ -337,12 +332,11 @@ export interface ZWaveProtocolCCTransferPresentationOptions @CCCommand(ZWaveProtocolCommand.TransferPresentation) export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCTransferPresentationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const option = this.payload[0]; @@ -366,13 +360,13 @@ export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { public includeNode: boolean; public excludeNode: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ (this.supportsNWI ? 0b0001 : 0) | (this.excludeNode ? 0b0010 : 0) | (this.includeNode ? 0b0100 : 0), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -389,12 +383,11 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC implements NodeProtocolInfoAndDeviceClass { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCTransferNodeInformationOptions, ) { - super(host, options); + super(options); let info: NodeProtocolInfoAndDeviceClass; if (gotDeserializationOptions(options)) { @@ -439,12 +432,12 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC public supportsSecurity: boolean; public supportsBeaming: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.sequenceNumber, this.sourceNodeId]), encodeNodeProtocolInfoAndDeviceClass(this), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -460,12 +453,11 @@ export interface ZWaveProtocolCCTransferRangeInformationOptions @CCCommand(ZWaveProtocolCommand.TransferRangeInformation) export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCTransferRangeInformationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 3); this.sequenceNumber = this.payload[0]; @@ -486,7 +478,7 @@ export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { public testedNodeId: number; public neighborNodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); this.payload = Buffer.concat([ Buffer.from([ @@ -496,7 +488,7 @@ export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { ]), nodesBitmask, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -508,12 +500,11 @@ export interface ZWaveProtocolCCTransferEndOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.TransferEnd) export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCTransferEndOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.status = this.payload[0]; @@ -524,9 +515,9 @@ export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { public status: NetworkTransferStatus; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.status]); - return super.serialize(); + return super.serialize(ctx); } } @@ -544,12 +535,11 @@ export interface ZWaveProtocolCCAssignReturnRouteOptions @CCCommand(ZWaveProtocolCommand.AssignReturnRoute) export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCAssignReturnRouteOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 7); this.destinationNodeId = this.payload[0]; @@ -583,7 +573,7 @@ export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { public destinationWakeUp: WakeUpTime; public destinationSpeed: ZWaveDataRate; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const routeByte = (this.routeIndex << 4) | this.repeaters.length; const speedMask = dataRate2Bitmask(this.destinationSpeed); const speedByte = (speedMask << 3) | (this.destinationWakeUp << 1); @@ -593,7 +583,7 @@ export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { ...this.repeaters, speedByte, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -609,12 +599,11 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC implements NodeInformationFrame { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNewNodeRegisteredOptions, ) { - super(host, options); + super(options); let nif: NodeInformationFrame; if (gotDeserializationOptions(options)) { @@ -656,12 +645,12 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC public supportsBeaming: boolean; public supportedCCs: CommandClasses[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.newNodeId]), encodeNodeInformationFrame(this), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -676,12 +665,11 @@ export interface ZWaveProtocolCCNewRangeRegisteredOptions @CCCommand(ZWaveProtocolCommand.NewRangeRegistered) export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNewRangeRegisteredOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.testedNodeId = this.payload[0]; @@ -698,13 +686,13 @@ export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { public testedNodeId: number; public neighborNodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const nodesBitmask = encodeBitMask(this.neighborNodeIds, MAX_NODES); this.payload = Buffer.concat([ Buffer.from([this.testedNodeId, nodesBitmask.length]), nodesBitmask, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -720,12 +708,11 @@ export class ZWaveProtocolCCTransferNewPrimaryControllerComplete extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.genericDeviceClass = this.payload[0]; @@ -736,9 +723,9 @@ export class ZWaveProtocolCCTransferNewPrimaryControllerComplete public genericDeviceClass: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.genericDeviceClass]); - return super.serialize(); + return super.serialize(ctx); } } @@ -756,12 +743,11 @@ export interface ZWaveProtocolCCSUCNodeIDOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SUCNodeID) export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCSUCNodeIDOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.sucNodeId = this.payload[0]; @@ -776,9 +762,9 @@ export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { public sucNodeId: number; public isSIS: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sucNodeId, this.isSIS ? 0b1 : 0]); - return super.serialize(); + return super.serialize(ctx); } } @@ -790,12 +776,11 @@ export interface ZWaveProtocolCCSetSUCOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SetSUC) export class ZWaveProtocolCCSetSUC extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCSetSUCOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); // Byte 0 must be 0x01 or ignored @@ -808,9 +793,9 @@ export class ZWaveProtocolCCSetSUC extends ZWaveProtocolCC { public enableSIS: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([0x01, this.enableSIS ? 0b1 : 0]); - return super.serialize(); + return super.serialize(ctx); } } @@ -823,12 +808,11 @@ export interface ZWaveProtocolCCSetSUCAckOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SetSUCAck) export class ZWaveProtocolCCSetSUCAck extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCSetSUCAckOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.accepted = this.payload[0] === 0x01; @@ -843,12 +827,12 @@ export class ZWaveProtocolCCSetSUCAck extends ZWaveProtocolCC { public accepted: boolean; public isSIS: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.accepted ? 0x01 : 0x00, this.isSIS ? 0b1 : 0, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -867,12 +851,11 @@ export interface ZWaveProtocolCCStaticRouteRequestOptions @CCCommand(ZWaveProtocolCommand.StaticRouteRequest) export class ZWaveProtocolCCStaticRouteRequest extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCStaticRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 5); this.nodeIds = [...this.payload.subarray(0, 5)].filter( @@ -891,12 +874,12 @@ export class ZWaveProtocolCCStaticRouteRequest extends ZWaveProtocolCC { public nodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.alloc(5, 0); for (let i = 0; i < this.nodeIds.length && i < 5; i++) { this.payload[i] = this.nodeIds[i]; } - return super.serialize(); + return super.serialize(ctx); } } @@ -908,12 +891,11 @@ export interface ZWaveProtocolCCLostOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.Lost) export class ZWaveProtocolCCLost extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCLostOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.lostNodeId = this.payload[0]; @@ -924,9 +906,9 @@ export class ZWaveProtocolCCLost extends ZWaveProtocolCC { public lostNodeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.lostNodeId]); - return super.serialize(); + return super.serialize(ctx); } } @@ -938,12 +920,11 @@ export interface ZWaveProtocolCCAcceptLostOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.AcceptLost) export class ZWaveProtocolCCAcceptLost extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCAcceptLostOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); validatePayload( @@ -957,9 +938,9 @@ export class ZWaveProtocolCCAcceptLost extends ZWaveProtocolCC { public accepted: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.accepted ? 0x05 : 0x04]); - return super.serialize(); + return super.serialize(ctx); } } @@ -971,12 +952,11 @@ export interface ZWaveProtocolCCNOPPowerOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.NOPPower) export class ZWaveProtocolCCNOPPower extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNOPPowerOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { if (this.payload.length >= 2) { // Ignore byte 0 @@ -1017,9 +997,9 @@ export class ZWaveProtocolCCNOPPower extends ZWaveProtocolCC { // Power dampening in (negative) dBm. A value of 2 means -2 dBm. public powerDampening: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([0, this.powerDampening]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1031,12 +1011,11 @@ export interface ZWaveProtocolCCReservedIDsOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.ReservedIDs) export class ZWaveProtocolCCReservedIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCReservedIDsOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); const numNodeIDs = this.payload[0]; @@ -1051,12 +1030,12 @@ export class ZWaveProtocolCCReservedIDs extends ZWaveProtocolCC { public reservedNodeIDs: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.reservedNodeIDs.length, ...this.reservedNodeIDs, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1069,12 +1048,11 @@ export interface ZWaveProtocolCCReserveNodeIDsOptions extends CCCommandOptions { @expectedCCResponse(ZWaveProtocolCCReservedIDs) export class ZWaveProtocolCCReserveNodeIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCReserveNodeIDsOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); this.numNodeIDs = this.payload[0]; @@ -1085,9 +1063,9 @@ export class ZWaveProtocolCCReserveNodeIDs extends ZWaveProtocolCC { public numNodeIDs: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.numNodeIDs]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1102,12 +1080,11 @@ export interface ZWaveProtocolCCNodesExistReplyOptions @CCCommand(ZWaveProtocolCommand.NodesExistReply) export class ZWaveProtocolCCNodesExistReply extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNodesExistReplyOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.nodeMaskType = this.payload[0]; @@ -1121,12 +1098,12 @@ export class ZWaveProtocolCCNodesExistReply extends ZWaveProtocolCC { public nodeMaskType: number; public nodeListUpdated: boolean; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.nodeMaskType, this.nodeListUpdated ? 0x01 : 0x00, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1150,12 +1127,11 @@ export interface ZWaveProtocolCCNodesExistOptions extends CCCommandOptions { ) export class ZWaveProtocolCCNodesExist extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCNodesExistOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.nodeMaskType = this.payload[0]; @@ -1171,13 +1147,13 @@ export class ZWaveProtocolCCNodesExist extends ZWaveProtocolCC { public nodeMaskType: number; public nodeIDs: number[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.nodeMaskType, this.nodeIDs.length, ...this.nodeIDs, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1190,12 +1166,11 @@ export interface ZWaveProtocolCCSetNWIModeOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SetNWIMode) export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCSetNWIModeOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.enabled = this.payload[0] === 0x01; @@ -1209,12 +1184,12 @@ export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { public enabled: boolean; public timeoutMinutes?: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.enabled ? 0x01 : 0x00, this.timeoutMinutes ?? 0x00, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1234,12 +1209,11 @@ export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions @CCCommand(ZWaveProtocolCommand.AssignReturnRoutePriority) export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCAssignReturnRoutePriorityOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.targetNodeId = this.payload[0]; @@ -1253,9 +1227,9 @@ export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { public targetNodeId: number; public routeNumber: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.targetNodeId, this.routeNumber]); - return super.serialize(); + return super.serialize(ctx); } } @@ -1276,12 +1250,11 @@ export class ZWaveProtocolCCSmartStartIncludedNodeInformation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | ZWaveProtocolCCSmartStartIncludedNodeInformationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 4); this.nwiHomeId = this.payload.subarray(0, 4); @@ -1298,9 +1271,9 @@ export class ZWaveProtocolCCSmartStartIncludedNodeInformation public nwiHomeId: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from(this.nwiHomeId); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/cc/src/cc/index.ts b/packages/cc/src/cc/index.ts index 1499572ca55b..11af0556c837 100644 --- a/packages/cc/src/cc/index.ts +++ b/packages/cc/src/cc/index.ts @@ -628,6 +628,7 @@ export type { Security2CCMessageEncapsulationOptions, Security2CCNetworkKeyGetOptions, Security2CCNetworkKeyReportOptions, + Security2CCNonceGetOptions, Security2CCNonceReportOptions, Security2CCPublicKeyReportOptions, Security2CCTransferEndOptions, diff --git a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts index 01ff2bccef2d..ca3329ef440d 100644 --- a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts +++ b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts @@ -11,9 +11,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; @@ -31,6 +31,9 @@ import { import { type CCCommandOptions, type CommandClassDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, gotDeserializationOptions, } from "../../lib/CommandClass"; import { expectedCCResponse } from "../../lib/CommandClassDecorators"; @@ -89,6 +92,20 @@ export function getFibaroVenetianBlindTiltMetadata(): ValueMetadata { }; } +function getSupportedFibaroCCIDs( + ctx: GetDeviceConfig, + nodeId: number, +): FibaroCCIDs[] { + const proprietaryConfig = ctx.getDeviceConfig?.( + nodeId, + )?.proprietary; + if (proprietaryConfig && isArray(proprietaryConfig.fibaroCCs)) { + return proprietaryConfig.fibaroCCs as FibaroCCIDs[]; + } + + return []; +} + export enum FibaroCCIDs { VenetianBlind = 0x26, } @@ -97,11 +114,11 @@ export enum FibaroCCIDs { export class FibaroCCAPI extends ManufacturerProprietaryCCAPI { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public async fibaroVenetianBlindsGet() { - const cc = new FibaroVenetianBlindCCGet(this.applHost, { + const cc = new FibaroVenetianBlindCCGet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FibaroVenetianBlindCCReport >( cc, @@ -114,22 +131,22 @@ export class FibaroCCAPI extends ManufacturerProprietaryCCAPI { @validateArgs() public async fibaroVenetianBlindsSetPosition(value: number): Promise { - const cc = new FibaroVenetianBlindCCSet(this.applHost, { + const cc = new FibaroVenetianBlindCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, position: value, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() public async fibaroVenetianBlindsSetTilt(value: number): Promise { - const cc = new FibaroVenetianBlindCCSet(this.applHost, { + const cc = new FibaroVenetianBlindCCSet({ nodeId: this.endpoint.nodeId, endpoint: this.endpoint.index, tilt: value, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } protected override get [SET_VALUE](): SetValueImplementation { @@ -201,10 +218,9 @@ export class FibaroCCAPI extends ManufacturerProprietaryCCAPI { @manufacturerId(MANUFACTURERID_FIBARO) export class FibaroCC extends ManufacturerProprietaryCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 2); this.fibaroCCId = this.payload[0]; @@ -218,7 +234,7 @@ export class FibaroCC extends ManufacturerProprietaryCC { FibaroConstructor && (new.target as any) !== FibaroConstructor ) { - return new FibaroConstructor(host, options); + return new FibaroConstructor(options); } this.payload = this.payload.subarray(2); @@ -231,54 +247,39 @@ export class FibaroCC extends ManufacturerProprietaryCC { public fibaroCCId?: number; public fibaroCCCommand?: number; - private getSupportedFibaroCCIDs( - applHost: ZWaveApplicationHost, - ): FibaroCCIDs[] { - const node = this.getNode(applHost)!; - - const proprietaryConfig = applHost.getDeviceConfig?.( - node.id, - )?.proprietary; - if (proprietaryConfig && isArray(proprietaryConfig.fibaroCCs)) { - return proprietaryConfig.fibaroCCs as FibaroCCIDs[]; - } - - return []; - } - - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview( + ctx: InterviewContext, + ): Promise { + const node = this.getNode(ctx)!; // Iterate through all supported Fibaro CCs and interview them - const supportedFibaroCCIDs = this.getSupportedFibaroCCIDs(applHost); + const supportedFibaroCCIDs = getSupportedFibaroCCIDs(ctx, node.id); for (const ccId of supportedFibaroCCIDs) { const SubConstructor = getFibaroCCConstructor(ccId); if (SubConstructor) { - const instance = new SubConstructor(this.host, { - nodeId: node.id, - }); - await instance.interview(applHost); + const instance = new SubConstructor({ nodeId: node.id }); + await instance.interview(ctx); } } } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async refreshValues( + ctx: RefreshValuesContext, + ): Promise { + const node = this.getNode(ctx)!; // Iterate through all supported Fibaro CCs and let them refresh their values - const supportedFibaroCCIDs = this.getSupportedFibaroCCIDs(applHost); + const supportedFibaroCCIDs = getSupportedFibaroCCIDs(ctx, node.id); for (const ccId of supportedFibaroCCIDs) { const SubConstructor = getFibaroCCConstructor(ccId); if (SubConstructor) { - const instance = new SubConstructor(this.host, { - nodeId: node.id, - }); - await instance.refreshValues(applHost); + const instance = new SubConstructor({ nodeId: node.id }); + await instance.refreshValues(ctx); } } } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (this.fibaroCCId == undefined) { throw new ZWaveError( "Cannot serialize a Fibaro CC without a Fibaro CC ID", @@ -294,7 +295,7 @@ export class FibaroCC extends ManufacturerProprietaryCC { Buffer.from([this.fibaroCCId, this.fibaroCCCommand]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -310,10 +311,9 @@ export class FibaroVenetianBlindCC extends FibaroCC { declare fibaroCCCommand: FibaroVenetianBlindCCCommand; public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); this.fibaroCCId = FibaroCCIDs.VenetianBlind; if (gotDeserializationOptions(options)) { @@ -321,33 +321,33 @@ export class FibaroVenetianBlindCC extends FibaroCC { this.fibaroCCCommand === FibaroVenetianBlindCCCommand.Report && (new.target as any) !== FibaroVenetianBlindCCReport ) { - return new FibaroVenetianBlindCCReport(host, options); + return new FibaroVenetianBlindCCReport(options); } } } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async interview(ctx: InterviewContext): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: `Interviewing Fibaro Venetian Blind CC...`, direction: "none", }); // Nothing special, just get the values - await this.refreshValues(applHost); + await this.refreshValues(ctx); } - public async refreshValues(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public async refreshValues(ctx: RefreshValuesContext): Promise { + const node = this.getNode(ctx)!; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "Requesting venetian blind position and tilt...", direction: "outbound", }); - const resp = await applHost.sendCommand( - new FibaroVenetianBlindCCGet(this.host, { + const resp = await ctx.sendCommand( + new FibaroVenetianBlindCCGet({ nodeId: this.nodeId, endpoint: this.endpointIndex, }), @@ -356,7 +356,7 @@ export class FibaroVenetianBlindCC extends FibaroCC { const logMessage = `received venetian blind state: position: ${resp.position} tilt: ${resp.tilt}`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -383,12 +383,11 @@ export type FibaroVenetianBlindCCSetOptions = @fibaroCCCommand(FibaroVenetianBlindCCCommand.Set) export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, options: | CommandClassDeserializationOptions | FibaroVenetianBlindCCSetOptions, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Set; if (Buffer.isBuffer(options)) { @@ -406,7 +405,7 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { public position: number | undefined; public tilt: number | undefined; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const controlByte = (this.position != undefined ? 0b10 : 0) | (this.tilt != undefined ? 0b01 : 0); this.payload = Buffer.from([ @@ -414,10 +413,10 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { this.position ?? 0, this.tilt ?? 0, ]); - return super.serialize(); + return super.serialize(ctx); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.position != undefined) { message.position = this.position; @@ -426,7 +425,7 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { message.tilt = this.tilt; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -435,10 +434,9 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { @fibaroCCCommand(FibaroVenetianBlindCCCommand.Report) export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Report; validatePayload(this.payload.length >= 3); @@ -453,9 +451,9 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; - const valueDB = this.getValueDB(applHost); + public persistValues(ctx: PersistValuesContext): boolean { + if (!super.persistValues(ctx)) return false; + const valueDB = this.getValueDB(ctx); if (this.position != undefined) { const positionValueId = getFibaroVenetianBlindPositionValueId( @@ -491,7 +489,7 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { return this._tilt; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = {}; if (this.position != undefined) { message.position = this.position; @@ -500,7 +498,7 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { message.tilt = this.tilt; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -510,10 +508,9 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { @expectedCCResponse(FibaroVenetianBlindCCReport) export class FibaroVenetianBlindCCGet extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, options: CommandClassDeserializationOptions | CCCommandOptions, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Get; } } diff --git a/packages/cc/src/lib/API.ts b/packages/cc/src/lib/API.ts index 9c9d7aad6e54..29bde3de01aa 100644 --- a/packages/cc/src/lib/API.ts +++ b/packages/cc/src/lib/API.ts @@ -1,27 +1,45 @@ import { type CompatOverrideQueries } from "@zwave-js/config"; import { CommandClasses, + type ControlsCC, type Duration, - type IVirtualEndpoint, - type IZWaveEndpoint, - type IZWaveNode, + type EndpointId, + type GetEndpoint, + type ListenBehavior, type MaybeNotKnown, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, NOT_KNOWN, + type NodeId, + type PhysicalNodes, + type QueryNodeStatus, + type SecurityManagers, type SendCommandOptions, type SupervisionResult, + type SupportsCC, type TXReport, type ValueChangeOptions, type ValueDB, type ValueID, + type VirtualEndpointId, ZWaveError, ZWaveErrorCodes, getCCName, - isZWaveError, stripUndefined, } from "@zwave-js/core"; -import type { ZWaveApplicationHost } from "@zwave-js/host"; +import type { + GetCommunicationTimeouts, + GetDeviceConfig, + GetNode, + GetSafeCCVersion, + GetSupportedCCVersion, + GetUserPreferences, + GetValueDB, + HostIDs, + LogNode, + SchedulePoll, + SendCommand, +} from "@zwave-js/host"; import { type AllOrNone, type OnlyMethods, @@ -148,22 +166,66 @@ export interface SchedulePollOptions { transition?: "fast" | "slow"; } +// Defines the necessary traits the host passed to a CC API must have +export type CCAPIHost = + & HostIDs + & GetNode + & GetValueDB + & GetSupportedCCVersion + & GetSafeCCVersion + & SecurityManagers + & GetDeviceConfig + & SendCommand + & GetCommunicationTimeouts + & GetUserPreferences + & SchedulePoll + & LogNode; + +// Defines the necessary traits a node passed to a CC API must have +export type CCAPINode = NodeId & ListenBehavior & QueryNodeStatus; + +// Defines the necessary traits an endpoint passed to a CC API must have +export type CCAPIEndpoint = + & ( + | ( + // Physical endpoints must let us query their controlled CCs + EndpointId & ControlsCC + ) + | ( + // Virtual endpoints must let us query their physical nodes, + // the CCs those nodes implement, and access the endpoints of those + // physical nodes + VirtualEndpointId & { + node: PhysicalNodes< + & NodeId + & SupportsCC + & ControlsCC + & GetEndpoint + >; + } + ) + ) + & SupportsCC; + +export type PhysicalCCAPIEndpoint = CCAPIEndpoint & EndpointId; +export type VirtualCCAPIEndpoint = CCAPIEndpoint & VirtualEndpointId; + /** * The base class for all CC APIs exposed via `Node.commandClasses.` * @publicAPI */ export class CCAPI { public constructor( - protected readonly applHost: ZWaveApplicationHost, - protected readonly endpoint: IZWaveEndpoint | IVirtualEndpoint, + protected readonly host: CCAPIHost, + protected readonly endpoint: CCAPIEndpoint, ) { this.ccId = getCommandClass(this); } public static create( ccId: T, - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + host: CCAPIHost, + endpoint: CCAPIEndpoint, requireSupport?: boolean, ): CommandClasses extends T ? CCAPI : CCToAPI { const APIConstructor = getAPI(ccId); @@ -178,7 +240,7 @@ export class CCAPI { ZWaveErrorCodes.CC_NoAPI, ); } - const apiInstance = new APIConstructor(applHost, endpoint); + const apiInstance = new APIConstructor(host, endpoint); // Only require support for physical endpoints by default requireSupport ??= !endpoint.virtual; @@ -227,12 +289,12 @@ export class CCAPI { && !endpoint.virtual && typeof fallback === "function" ) { - const overrides = applHost.getDeviceConfig?.( + const overrides = host.getDeviceConfig?.( endpoint.nodeId, )?.compat?.overrideQueries; if (overrides?.hasOverride(ccId)) { return overrideQueriesWrapper( - applHost, + host, endpoint, ccId, property, @@ -306,16 +368,17 @@ export class CCAPI { // Figure out the delay. If a non-zero duration was given or this is a "fast" transition, // use/add the short delay. Otherwise, default to the long delay. const durationMs = duration?.toMilliseconds() ?? 0; + const timeouts = this.host.getCommunicationTimeouts(); const additionalDelay = !!durationMs || transition === "fast" - ? this.applHost.options.timeouts.refreshValueAfterTransition - : this.applHost.options.timeouts.refreshValue; + ? timeouts.refreshValueAfterTransition + : timeouts.refreshValue; const timeoutMs = durationMs + additionalDelay; if (this.isSinglecast()) { - const node = this.endpoint.getNodeUnsafe(); + const node = this.host.getNode(this.endpoint.nodeId); if (!node) return false; - return this.applHost.schedulePoll( + return this.host.schedulePoll( node.id, { commandClass: this.ccId, @@ -335,7 +398,7 @@ export class CCAPI { ); let ret = false; for (const node of supportingNodes) { - ret ||= this.applHost.schedulePoll( + ret ||= this.host.schedulePoll( node.id, { commandClass: this.ccId, @@ -358,11 +421,11 @@ export class CCAPI { */ public get version(): number { if (this.isSinglecast()) { - return this.applHost.getSafeCCVersion( + return this.host.getSafeCCVersion( this.ccId, this.endpoint.nodeId, this.endpoint.index, - ); + ) ?? 0; } else { return getImplementedVersion(this.ccId); } @@ -416,8 +479,8 @@ export class CCAPI { } protected assertPhysicalEndpoint( - endpoint: IZWaveEndpoint | IVirtualEndpoint, - ): asserts endpoint is IZWaveEndpoint { + endpoint: EndpointId | VirtualEndpointId, + ): asserts endpoint is EndpointId { if (endpoint.virtual) { throw new ZWaveError( `This method is not supported for virtual nodes!`, @@ -516,7 +579,9 @@ export class CCAPI { }) as any; } - protected isSinglecast(): this is this & { endpoint: IZWaveEndpoint } { + protected isSinglecast(): this is this & { + endpoint: PhysicalCCAPIEndpoint; + } { return ( !this.endpoint.virtual && typeof this.endpoint.nodeId === "number" @@ -526,7 +591,7 @@ export class CCAPI { } protected isMulticast(): this is this & { - endpoint: IVirtualEndpoint & { + endpoint: VirtualCCAPIEndpoint & { nodeId: number[]; }; } { @@ -534,7 +599,7 @@ export class CCAPI { } protected isBroadcast(): this is this & { - endpoint: IVirtualEndpoint & { + endpoint: VirtualCCAPIEndpoint & { nodeId: typeof NODE_ID_BROADCAST | typeof NODE_ID_BROADCAST_LR; }; } { @@ -545,37 +610,11 @@ export class CCAPI { ); } - /** - * Returns the node this CC API is linked to. Throws if the controller is not yet ready. - */ - public getNode(): IZWaveNode | undefined { - if (this.isSinglecast()) { - return this.applHost.nodes.get(this.endpoint.nodeId); - } - } - - /** - * @internal - * Returns the node this CC API is linked to (or undefined if the node doesn't exist) - */ - public getNodeUnsafe(): IZWaveNode | undefined { - try { - return this.getNode(); - } catch (e) { - // This was expected - if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_NotReady) { - return undefined; - } - // Something else happened - throw e; - } - } - /** Returns the value DB for this CC API's node (if it can be safely accessed) */ protected tryGetValueDB(): ValueDB | undefined { if (!this.isSinglecast()) return; try { - return this.applHost.getValueDB(this.endpoint.nodeId); + return this.host.getValueDB(this.endpoint.nodeId); } catch { return; } @@ -585,7 +624,7 @@ export class CCAPI { protected getValueDB(): ValueDB { if (this.isSinglecast()) { try { - return this.applHost.getValueDB(this.endpoint.nodeId); + return this.host.getValueDB(this.endpoint.nodeId); } catch { throw new ZWaveError( "The node for this CC does not exist or the driver is not ready yet", @@ -601,8 +640,8 @@ export class CCAPI { } function overrideQueriesWrapper( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & LogNode, + endpoint: PhysicalCCAPIEndpoint, ccId: CommandClasses, method: string, overrides: CompatOverrideQueries, @@ -618,7 +657,7 @@ function overrideQueriesWrapper( ); if (!match) return fallback.call(this, ...args); - applHost.controllerLog.logNode(endpoint.nodeId, { + ctx.logNode(endpoint.nodeId, { message: `API call ${method} for ${ getCCName( ccId, @@ -630,7 +669,7 @@ function overrideQueriesWrapper( const ccValues = getCCValues(ccId); if (ccValues) { - const valueDB = applHost.getValueDB(endpoint.nodeId); + const valueDB = ctx.getValueDB(endpoint.nodeId); const prop2value = (prop: string): CCValue | undefined => { // We use a simplistic parser to support dynamic value IDs: @@ -670,7 +709,7 @@ function overrideQueriesWrapper( value, ); } else { - applHost.controllerLog.logNode(endpoint.nodeId, { + ctx.logNode(endpoint.nodeId, { message: `Failed to persist value ${prop} during overridden API call: value does not exist`, level: "error", @@ -678,7 +717,7 @@ function overrideQueriesWrapper( }); } } catch (e) { - applHost.controllerLog.logNode(endpoint.nodeId, { + ctx.logNode(endpoint.nodeId, { message: `Failed to persist value ${prop} during overridden API call: ${ getErrorMessage( @@ -710,7 +749,7 @@ function overrideQueriesWrapper( }, ); } else { - applHost.controllerLog.logNode(endpoint.nodeId, { + ctx.logNode(endpoint.nodeId, { message: `Failed to extend value metadata ${prop} during overridden API call: value does not exist`, level: "error", @@ -718,7 +757,7 @@ function overrideQueriesWrapper( }); } } catch (e) { - applHost.controllerLog.logNode(endpoint.nodeId, { + ctx.logNode(endpoint.nodeId, { message: `Failed to extend value metadata ${prop} during overridden API call: ${ getErrorMessage( @@ -741,19 +780,19 @@ function overrideQueriesWrapper( /** A CC API that is only available for physical endpoints */ export class PhysicalCCAPI extends CCAPI { public constructor( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + host: CCAPIHost, + endpoint: CCAPIEndpoint, ) { - super(applHost, endpoint); + super(host, endpoint); this.assertPhysicalEndpoint(endpoint); } - declare protected readonly endpoint: IZWaveEndpoint; + declare protected readonly endpoint: PhysicalCCAPIEndpoint; } export type APIConstructor = new ( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint | IVirtualEndpoint, + host: CCAPIHost, + endpoint: CCAPIEndpoint, ) => T; // This type is auto-generated by maintenance/generateCCAPIInterface.ts @@ -849,7 +888,7 @@ export type APIMethodsOf = Omit< OnlyMethods>, | "ccId" | "getNode" - | "getNodeUnsafe" + | "tryGetNode" | "isSetValueOptimistic" | "isSupported" | "pollValue" diff --git a/packages/cc/src/lib/CommandClass.ts b/packages/cc/src/lib/CommandClass.ts index c8674e77243d..cc02852ffb5f 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -1,18 +1,28 @@ import { type BroadcastCC, + type CCId, CommandClasses, + type ControlsCC, EncapsulationFlags, + type EndpointId, type FrameType, - type ICommandClass, - type IZWaveEndpoint, - type IZWaveNode, + type GetAllEndpoints, + type GetCCs, + type GetEndpoint, + type ListenBehavior, type MessageOrCCLogEntry, type MessageRecord, + type ModifyCCs, type MulticastCC, type MulticastDestination, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, + type NodeId, + type QueryNodeStatus, + type QuerySecurityClasses, + type SetSecurityClass, type SinglecastCC, + type SupportsCC, type ValueDB, type ValueID, type ValueMetadata, @@ -24,9 +34,15 @@ import { valueIdToString, } from "@zwave-js/core"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetInterviewOptions, + GetNode, + GetSupportedCCVersion, + GetValueDB, + LogNode, + LookupManufacturer, } from "@zwave-js/host"; import { MessageOrigin } from "@zwave-js/serial"; import { @@ -37,7 +53,7 @@ import { staticExtends, } from "@zwave-js/shared"; import { isArray } from "alcalzone-shared/typeguards"; -import type { ValueIDProperties } from "./API"; +import type { CCAPIHost, CCAPINode, ValueIDProperties } from "./API"; import { getCCCommand, getCCCommandConstructor, @@ -69,8 +85,7 @@ export type CommandClassDeserializationOptions = & { data: Buffer; origin?: MessageOrigin; - /** If known, the frame type of the containing message */ - frameType?: FrameType; + context: CCParsingContext; } & ( | { @@ -109,17 +124,85 @@ export type CommandClassOptions = | CommandClassCreationOptions | CommandClassDeserializationOptions; +// Defines the necessary traits an endpoint passed to a CC instance must have +export type CCEndpoint = + & EndpointId + & SupportsCC + & ControlsCC + & GetCCs + & ModifyCCs; + +// Defines the necessary traits a node passed to a CC instance must have +export type CCNode = + & NodeId + & SupportsCC + & ControlsCC + & GetCCs + & GetEndpoint + & GetAllEndpoints + & QuerySecurityClasses + & SetSecurityClass + & ListenBehavior + & QueryNodeStatus; + +export type InterviewContext = + & CCAPIHost< + & CCAPINode + & GetCCs + & SupportsCC + & ControlsCC + & QuerySecurityClasses + & SetSecurityClass + & GetEndpoint + & GetAllEndpoints + > + & GetInterviewOptions + & LookupManufacturer; + +export type RefreshValuesContext = CCAPIHost< + CCAPINode & GetEndpoint +>; + +export type PersistValuesContext = + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + > + & LogNode; + +export function getEffectiveCCVersion( + ctx: GetSupportedCCVersion, + cc: CCId, + defaultVersion?: number, +): number { + // For multicast and broadcast CCs, just use the highest implemented version to serialize + // Older nodes will ignore the additional fields + if ( + typeof cc.nodeId !== "number" + || cc.nodeId === NODE_ID_BROADCAST + || cc.nodeId === NODE_ID_BROADCAST_LR + ) { + return getImplementedVersion(cc.ccId); + } + // For singlecast CCs, set the CC version as high as possible + return ctx.getSupportedCCVersion(cc.ccId, cc.nodeId, cc.endpointIndex) + || (defaultVersion ?? getImplementedVersion(cc.ccId)); +} + // @publicAPI -export class CommandClass implements ICommandClass { +export class CommandClass implements CCId { // empty constructor to parse messages - public constructor(host: ZWaveHost, options: CommandClassOptions) { - this.host = host; + public constructor(options: CommandClassOptions) { // Default to the root endpoint - Inherited classes may override this behavior this.endpointIndex = ("endpoint" in options ? options.endpoint : undefined) ?? 0; - // We cannot use @ccValue for non-derived classes, so register interviewComplete as an internal value here - // this.registerValue("interviewComplete", { internal: true }); + this.origin = options.origin + ?? (gotDeserializationOptions(options) + ? MessageOrigin.Controller + : MessageOrigin.Host); if (gotDeserializationOptions(options)) { // For deserialized commands, try to invoke the correct subclass constructor @@ -137,7 +220,7 @@ export class CommandClass implements ICommandClass { CommandConstructor && (new.target as any) !== CommandConstructor ) { - return new CommandConstructor(host, options); + return new CommandConstructor(options); } } @@ -154,7 +237,7 @@ export class CommandClass implements ICommandClass { this.nodeId = options.nodeId; } - this.frameType = options.frameType; + this.frameType = options.context.frameType; ({ ccId: this.ccId, @@ -175,55 +258,8 @@ export class CommandClass implements ICommandClass { this.ccCommand = ccCommand; this.payload = payload; } - - if (this instanceof InvalidCC) return; - - if (options.origin !== MessageOrigin.Host && this.isSinglecast()) { - try { - // For singlecast CCs, set the CC version as high as possible - this.version = this.host.getSafeCCVersion( - this.ccId, - this.nodeId, - this.endpointIndex, - ); - // But remember which version the node supports - this._knownVersion = this.host.getSupportedCCVersion( - this.ccId, - this.nodeId, - this.endpointIndex, - ); - } catch (e) { - if ( - isZWaveError(e) - && e.code === ZWaveErrorCodes.CC_NotImplemented - ) { - // Someone tried to create a CC that is not implemented. Just set all versions to 0. - this.version = 0; - this._knownVersion = 0; - } else { - throw e; - } - } - - // Send secure commands if necessary - this.toggleEncapsulationFlag( - EncapsulationFlags.Security, - this.host.isCCSecure( - this.ccId, - this.nodeId, - this.endpointIndex, - ), - ); - } else { - // For multicast and broadcast CCs, we just use the highest implemented version to serialize - // Older nodes will ignore the additional fields - this.version = getImplementedVersion(this.ccId); - this._knownVersion = this.version; - } } - protected host: ZWaveHost; - /** This CC's identifier */ public ccId!: CommandClasses; public ccCommand?: number; @@ -237,16 +273,11 @@ export class CommandClass implements ICommandClass { // Work around https://github.com/Microsoft/TypeScript/issues/27555 public payload!: Buffer; - /** The version of the command class used */ - // Work around https://github.com/Microsoft/TypeScript/issues/27555 - public version!: number; - - /** The version of the CC the node has reported support for */ - private _knownVersion!: number; - /** Which endpoint of the node this CC belongs to. 0 for the root device. */ public endpointIndex: number; + public origin: MessageOrigin; + /** * Which encapsulation CCs this CC is/was/should be encapsulated with. * @@ -278,7 +309,7 @@ export class CommandClass implements ICommandClass { } /** Whether the interview for this CC was previously completed */ - public isInterviewComplete(host: ZWaveValueHost): boolean { + public isInterviewComplete(host: GetValueDB): boolean { return !!this.getValueDB(host).getValue({ commandClass: this.ccId, endpoint: this.endpointIndex, @@ -288,7 +319,7 @@ export class CommandClass implements ICommandClass { /** Marks the interview for this CC as complete or not */ public setInterviewComplete( - host: ZWaveValueHost, + host: GetValueDB, complete: boolean, ): void { this.getValueDB(host).setValue( @@ -327,7 +358,8 @@ export class CommandClass implements ICommandClass { /** * Serializes this CommandClass to be embedded in a message payload or another CC */ - public serialize(): Buffer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public serialize(ctx: CCEncodingContext): Buffer { // NoOp CCs have no command and no payload if (this.ccId === CommandClasses["No Operation"]) { return Buffer.from([this.ccId]); @@ -386,7 +418,6 @@ export class CommandClass implements ICommandClass { * Creates an instance of the CC that is serialized in the given buffer */ public static from( - host: ZWaveHost, options: CommandClassDeserializationOptions, ): CommandClass { // Fall back to unspecified command class in case we receive one that is not implemented @@ -394,7 +425,7 @@ export class CommandClass implements ICommandClass { const Constructor = getCCConstructor(ccId) ?? CommandClass; try { - return new Constructor(host, options); + return new Constructor(options); } catch (e) { // Indicate invalid payloads with a special CC type if ( @@ -424,7 +455,7 @@ export class CommandClass implements ICommandClass { reason = e.context; } - const ret = new InvalidCC(host, { + const ret = new InvalidCC({ nodeId, ccId, ccName, @@ -448,13 +479,12 @@ export class CommandClass implements ICommandClass { * **INTERNAL:** Applications should not use this directly. */ public static createInstanceUnchecked( - host: ZWaveHost, - endpoint: IZWaveEndpoint, + endpoint: EndpointId, cc: CommandClasses | CCConstructor, ): T | undefined { const Constructor = typeof cc === "number" ? getCCConstructor(cc) : cc; if (Constructor) { - return new Constructor(host, { + return new Constructor({ nodeId: endpoint.nodeId, endpoint: endpoint.index, }) as T; @@ -462,7 +492,7 @@ export class CommandClass implements ICommandClass { } /** Generates a representation of this CC for the log */ - public toLogEntry(_host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(_ctx?: GetValueDB): MessageOrCCLogEntry { let tag = this.constructor.name; const message: MessageRecord = {}; if (this.constructor === CommandClass) { @@ -514,14 +544,14 @@ export class CommandClass implements ICommandClass { /** * Performs the interview procedure for this CC according to SDS14223 */ - public async interview(_applHost: ZWaveApplicationHost): Promise { + public async interview(_ctx: InterviewContext): Promise { // This needs to be overwritten per command class. In the default implementation, don't do anything } /** * Refreshes all dynamic values of this CC */ - public async refreshValues(_applHost: ZWaveApplicationHost): Promise { + public async refreshValues(_ctx: RefreshValuesContext): Promise { // This needs to be overwritten per command class. In the default implementation, don't do anything } @@ -531,7 +561,13 @@ export class CommandClass implements ICommandClass { */ public shouldRefreshValues( this: SinglecastCC, - _applHost: ZWaveApplicationHost, + _ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, ): boolean { // This needs to be overwritten per command class. // In the default implementation, don't require a refresh @@ -563,7 +599,7 @@ export class CommandClass implements ICommandClass { * @param _value The value of the received BasicCC */ public setMappedBasicValue( - _applHost: ZWaveApplicationHost, + _ctx: GetValueDB, _value: number, ): boolean { // By default, don't map @@ -605,9 +641,11 @@ export class CommandClass implements ICommandClass { /** * Returns the node this CC is linked to. Throws if the controller is not yet ready. */ - public getNode(applHost: ZWaveApplicationHost): IZWaveNode | undefined { + public getNode( + ctx: GetNode, + ): T | undefined { if (this.isSinglecast()) { - return applHost.nodes.get(this.nodeId); + return ctx.getNode(this.nodeId); } } @@ -615,11 +653,11 @@ export class CommandClass implements ICommandClass { * @internal * Returns the node this CC is linked to (or undefined if the node doesn't exist) */ - public getNodeUnsafe( - applHost: ZWaveApplicationHost, - ): IZWaveNode | undefined { + public tryGetNode( + ctx: GetNode, + ): T | undefined { try { - return this.getNode(applHost); + return this.getNode(ctx); } catch (e) { // This was expected if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_NotReady) { @@ -630,17 +668,17 @@ export class CommandClass implements ICommandClass { } } - public getEndpoint( - applHost: ZWaveApplicationHost, - ): IZWaveEndpoint | undefined { - return this.getNode(applHost)?.getEndpoint(this.endpointIndex); + public getEndpoint( + ctx: GetNode>, + ): T | undefined { + return this.getNode(ctx)?.getEndpoint(this.endpointIndex); } /** Returns the value DB for this CC's node */ - protected getValueDB(host: ZWaveValueHost): ValueDB { + protected getValueDB(ctx: GetValueDB): ValueDB { if (this.isSinglecast()) { try { - return host.getValueDB(this.nodeId); + return ctx.getValueDB(this.nodeId); } catch { throw new ZWaveError( "The node for this CC does not exist or the driver is not ready yet", @@ -660,11 +698,11 @@ export class CommandClass implements ICommandClass { * @param meta Will be used in place of the predefined metadata when given */ protected ensureMetadata( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, meta?: ValueMetadata, ): void { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); if (!valueDB.hasMetadata(valueId)) { valueDB.setMetadata(valueId, meta ?? ccValue.meta); @@ -676,10 +714,10 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected removeMetadata( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, ): void { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); valueDB.setMetadata(valueId, undefined); } @@ -690,11 +728,11 @@ export class CommandClass implements ICommandClass { * @param meta Will be used in place of the predefined metadata when given */ protected setMetadata( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, meta?: ValueMetadata, ): void { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); valueDB.setMetadata(valueId, meta ?? ccValue.meta); } @@ -704,10 +742,10 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected getMetadata( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, ): T | undefined { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); return valueDB.getMetadata(valueId) as any; } @@ -717,11 +755,11 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected setValue( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, value: unknown, ): void { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); valueDB.setValue(valueId, value); } @@ -731,10 +769,10 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected removeValue( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, ): void { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); valueDB.removeValue(valueId); } @@ -744,10 +782,10 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected getValue( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, ): T | undefined { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); return valueDB.getValue(valueId); } @@ -757,10 +795,10 @@ export class CommandClass implements ICommandClass { * The endpoint index of the current CC instance is automatically taken into account. */ protected getValueTimestamp( - host: ZWaveValueHost, + ctx: GetValueDB, ccValue: CCValue, ): number | undefined { - const valueDB = this.getValueDB(host); + const valueDB = this.getValueDB(ctx); const valueId = ccValue.endpoint(this.endpointIndex); return valueDB.getTimestamp(valueId); } @@ -798,22 +836,32 @@ export class CommandClass implements ICommandClass { } private shouldAutoCreateValue( - applHost: ZWaveApplicationHost, + ctx: GetValueDB & GetDeviceConfig, value: StaticCCValue, ): boolean { return ( value.options.autoCreate === true || (typeof value.options.autoCreate === "function" && value.options.autoCreate( - applHost, - this.getEndpoint(applHost)!, + ctx, + { + virtual: false, + nodeId: this.nodeId as number, + index: this.endpointIndex, + }, )) ); } /** Returns a list of all value names that are defined for this CommandClass */ public getDefinedValueIDs( - applHost: ZWaveApplicationHost, + ctx: + & GetValueDB + & GetSupportedCCVersion + & GetDeviceConfig + & GetNode< + NodeId & GetEndpoint + >, includeInternal: boolean = false, ): ValueID[] { // In order to compare value ids, we need them to be strings @@ -834,13 +882,27 @@ export class CommandClass implements ICommandClass { }; // Return all value IDs for this CC... - const valueDB = this.getValueDB(applHost); + const valueDB = this.getValueDB(ctx); // ...which either have metadata or a value const existingValueIds: ValueID[] = [ ...valueDB.getValues(this.ccId), ...valueDB.getAllMetadata(this.ccId), ]; + // To determine which value IDs to expose, we need to know the CC version + // that we're doing this for + const supportedVersion = typeof this.nodeId === "number" + && this.nodeId !== NODE_ID_BROADCAST + && this.nodeId !== NODE_ID_BROADCAST_LR + // On singlecast CCs, use the version the node reported support for, + ? ctx.getSupportedCCVersion( + this.ccId, + this.nodeId, + this.endpointIndex, + ) + // on multicast/broadcast, use the highest version we implement + : getImplementedVersion(this.ccId); + // ...or which are statically defined using @ccValues(...) for (const value of Object.values(getCCValues(this) ?? {})) { // Skip dynamic CC values - they need a specific subclass instance to be evaluated @@ -849,7 +911,7 @@ export class CommandClass implements ICommandClass { // Skip those values that are only supported in higher versions of the CC if ( value.options.minVersion != undefined - && value.options.minVersion > this._knownVersion + && value.options.minVersion > supportedVersion ) { continue; } @@ -858,7 +920,7 @@ export class CommandClass implements ICommandClass { if (value.options.internal && !includeInternal) continue; // And determine if this value should be automatically "created" - if (!this.shouldAutoCreateValue(applHost, value)) continue; + if (!this.shouldAutoCreateValue(ctx, value)) continue; existingValueIds.push(value.endpoint(this.endpointIndex)); } @@ -908,14 +970,24 @@ export class CommandClass implements ICommandClass { * Persists all values for this CC instance into the value DB which are annotated with @ccValue. * Returns `true` if the process succeeded, `false` if the value DB cannot be accessed. */ - public persistValues(applHost: ZWaveApplicationHost): boolean { + public persistValues(ctx: PersistValuesContext): boolean { let valueDB: ValueDB; try { - valueDB = this.getValueDB(applHost); + valueDB = this.getValueDB(ctx); } catch { return false; } + // To determine which value IDs to expose, we need to know the CC version + // that we're doing this for + const supportedVersion = ctx.getSupportedCCVersion( + this.ccId, + // Values are only persisted for singlecast, so we know nodeId is a number + this.nodeId as number, + this.endpointIndex, + // If the version isn't known yet, limit the created values to V1 + ) || 1; + // Get all properties of this CC which are annotated with a @ccValue decorator and store them. for (const [prop, _value] of getCCValueProperties(this)) { // Evaluate dynamic CC values first @@ -924,7 +996,7 @@ export class CommandClass implements ICommandClass { // Skip those values that are only supported in higher versions of the CC if ( value.options.minVersion != undefined - && value.options.minVersion > this.version + && value.options.minVersion > supportedVersion ) { continue; } @@ -938,8 +1010,8 @@ export class CommandClass implements ICommandClass { && (sourceValue != undefined // ... or if we know which CC version the node supports // and the value may be automatically created - || (this._knownVersion >= value.options.minVersion - && this.shouldAutoCreateValue(applHost, value))); + || (supportedVersion >= value.options.minVersion + && this.shouldAutoCreateValue(ctx, value))); if (createMetadata && !valueDB.hasMetadata(valueId)) { valueDB.setMetadata(valueId, value.meta); @@ -974,10 +1046,9 @@ export class CommandClass implements ICommandClass { } /** Include previously received partial responses into a final CC */ - /* istanbul ignore next */ public mergePartialCCs( - _applHost: ZWaveApplicationHost, _partials: CommandClass[], + _ctx: CCParsingContext, ): void { // This is highly CC dependent // Overwrite this in derived classes, by default do nothing @@ -1063,7 +1134,7 @@ export class CommandClass implements ICommandClass { * @param _propertyKey The (optional) property key the translated name may depend on */ public translateProperty( - _applHost: ZWaveApplicationHost, + _ctx: GetValueDB, property: string | number, _propertyKey?: string | number, ): string { @@ -1077,7 +1148,7 @@ export class CommandClass implements ICommandClass { * @param propertyKey The property key for which the speaking name should be retrieved */ public translatePropertyKey( - _applHost: ZWaveApplicationHost, + _ctx: GetValueDB, _property: string | number, propertyKey: string | number, ): string | undefined { @@ -1168,8 +1239,8 @@ export interface InvalidCCCreationOptions extends CommandClassCreationOptions { } export class InvalidCC extends CommandClass { - public constructor(host: ZWaveHost, options: InvalidCCCreationOptions) { - super(host, options); + public constructor(options: InvalidCCCreationOptions) { + super(options); this._ccName = options.ccName; // Numeric reasons are used internally to communicate problems with a CC // without ignoring them entirely @@ -1182,7 +1253,7 @@ export class InvalidCC extends CommandClass { } public readonly reason?: string | ZWaveErrorCodes; - public toLogEntry(_host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(_ctx?: GetValueDB): MessageOrCCLogEntry { return { tags: [this.ccName, "INVALID"], message: this.reason != undefined @@ -1221,7 +1292,7 @@ export function assertValidCCs(container: ICommandClassContainer): void { export type CCConstructor = typeof CommandClass & { // I don't like the any, but we need it to support half-implemented CCs (e.g. report classes) - new (host: ZWaveHost, options: any): T; + new (options: any): T; }; /** diff --git a/packages/cc/src/lib/EncapsulatingCommandClass.ts b/packages/cc/src/lib/EncapsulatingCommandClass.ts index 246a4f980069..5f9103e47682 100644 --- a/packages/cc/src/lib/EncapsulatingCommandClass.ts +++ b/packages/cc/src/lib/EncapsulatingCommandClass.ts @@ -1,26 +1,11 @@ -import type { ZWaveApplicationHost } from "@zwave-js/host"; import { isArray } from "alcalzone-shared/typeguards"; -import { CommandClass, type CommandClassOptions } from "./CommandClass"; - -/** Defines the static side of an encapsulating command class */ -export interface EncapsulatingCommandClassStatic { - new ( - applHost: ZWaveApplicationHost, - options: CommandClassOptions, - ): EncapsulatingCommandClass; - - encapsulate( - applHost: ZWaveApplicationHost, - cc: CommandClass, - ): EncapsulatingCommandClass; -} +import { CommandClass } from "./CommandClass"; export type EncapsulatedCommandClass = CommandClass & { encapsulatingCC: EncapsulatingCommandClass; }; export type EncapsulatingCommandClass = CommandClass & { - constructor: EncapsulatingCommandClassStatic; encapsulated: EncapsulatedCommandClass; }; @@ -57,22 +42,7 @@ export function getInnermostCommandClass(cc: CommandClass): CommandClass { } } -/** Defines the static side of an encapsulating command class */ -export interface MultiEncapsulatingCommandClassStatic { - new ( - applHost: ZWaveApplicationHost, - options: CommandClassOptions, - ): MultiEncapsulatingCommandClass; - - requiresEncapsulation(cc: CommandClass): boolean; - encapsulate( - applHost: ZWaveApplicationHost, - CCs: CommandClass[], - ): MultiEncapsulatingCommandClass; -} - export interface MultiEncapsulatingCommandClass { - constructor: MultiEncapsulatingCommandClassStatic; encapsulated: EncapsulatedCommandClass[]; } diff --git a/packages/cc/src/lib/Values.ts b/packages/cc/src/lib/Values.ts index e83952b480d6..ae4f074093b9 100644 --- a/packages/cc/src/lib/Values.ts +++ b/packages/cc/src/lib/Values.ts @@ -1,10 +1,10 @@ import { type CommandClasses, - type IZWaveEndpoint, + type EndpointId, type ValueID, ValueMetadata, } from "@zwave-js/core"; -import type { ZWaveApplicationHost } from "@zwave-js/host"; +import type { GetDeviceConfig, GetValueDB } from "@zwave-js/host"; import { type FnOrStatic, type ReturnTypeOrStatic, @@ -48,8 +48,8 @@ export interface CCValueOptions { autoCreate?: | boolean | (( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & GetDeviceConfig, + endpoint: EndpointId, ) => boolean); } diff --git a/packages/cc/src/lib/_Types.ts b/packages/cc/src/lib/_Types.ts index 14a1e68e75eb..870ed6749a97 100644 --- a/packages/cc/src/lib/_Types.ts +++ b/packages/cc/src/lib/_Types.ts @@ -1512,6 +1512,7 @@ export enum ThermostatSetpointType { "Away Heating" = 0x0d, // CC v2 "Away Cooling" = 0x0e, // CC v3 "Full Power" = 0x0f, // CC v3 + // Update the interview procecure when adding new types } export interface ThermostatSetpointValue { diff --git a/packages/cc/src/lib/utils.ts b/packages/cc/src/lib/utils.ts index 3ed1b4ada4a6..6a5d7d617db4 100644 --- a/packages/cc/src/lib/utils.ts +++ b/packages/cc/src/lib/utils.ts @@ -1,11 +1,16 @@ import type { AssociationConfig } from "@zwave-js/config"; import { CommandClasses, - type IZWaveEndpoint, - type IZWaveNode, + type ControlsCC, + type EndpointId, + type GetAllEndpoints, + type GetEndpoint, type MaybeNotKnown, NOT_KNOWN, + type NodeId, + type QuerySecurityClasses, SecurityClass, + type SupportsCC, ZWaveError, ZWaveErrorCodes, actuatorCCs, @@ -14,7 +19,12 @@ import { isLongRangeNodeId, isSensorCC, } from "@zwave-js/core/safe"; -import type { ZWaveApplicationHost } from "@zwave-js/host/safe"; +import { + type GetDeviceConfig, + type GetNode, + type GetValueDB, + type HostIDs, +} from "@zwave-js/host"; import { ObjectKeyMap, type ReadonlyObjectKeyMap, @@ -24,7 +34,7 @@ import { distinct } from "alcalzone-shared/arrays"; import { AssociationCC, AssociationCCValues } from "../cc/AssociationCC"; import { AssociationGroupInfoCC } from "../cc/AssociationGroupInfoCC"; import { MultiChannelAssociationCC } from "../cc/MultiChannelAssociationCC"; -import { CCAPI } from "./API"; +import { CCAPI, type CCAPIHost, type CCAPINode } from "./API"; import { type AssociationAddress, AssociationCheckResult, @@ -34,14 +44,14 @@ import { } from "./_Types"; export function getAssociations( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB, + endpoint: EndpointId & SupportsCC, ): ReadonlyMap { const ret = new Map(); if (endpoint.supportsCC(CommandClasses.Association)) { const destinations = AssociationCC.getAllDestinationsCached( - applHost, + ctx, endpoint, ); for (const [groupId, assocs] of destinations) { @@ -59,7 +69,7 @@ export function getAssociations( // Merge the "normal" destinations with multi channel destinations if (endpoint.supportsCC(CommandClasses["Multi Channel Association"])) { const destinations = MultiChannelAssociationCC.getAllDestinationsCached( - applHost, + ctx, endpoint, ); for (const [groupId, assocs] of destinations) { @@ -87,8 +97,8 @@ export function getAssociations( } export function getAllAssociations( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + node: NodeId & GetAllEndpoints, ): ReadonlyObjectKeyMap< AssociationAddress, ReadonlyMap @@ -103,21 +113,29 @@ export function getAllAssociations( endpoint: endpoint.index, }; if (endpoint.supportsCC(CommandClasses.Association)) { - ret.set(address, getAssociations(applHost, endpoint)); + ret.set(address, getAssociations(ctx, endpoint)); } } return ret; } export function checkAssociation( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: + & HostIDs + & GetValueDB + & GetNode< + & NodeId + & SupportsCC + & GetEndpoint + & QuerySecurityClasses + >, + endpoint: EndpointId & SupportsCC & ControlsCC, group: number, destination: AssociationAddress, ): AssociationCheckResult { // Check that the target endpoint exists except when adding an association to the controller - const targetNode = applHost.nodes.getOrThrow(destination.nodeId); - const targetEndpoint = destination.nodeId === applHost.ownNodeId + const targetNode = ctx.getNodeOrThrow(destination.nodeId); + const targetEndpoint = destination.nodeId === ctx.ownNodeId ? targetNode : targetNode.getEndpointOrThrow(destination.endpoint ?? 0); @@ -138,13 +156,13 @@ export function checkAssociation( return AssociationCheckResult.Forbidden_DestinationIsLongRange; } else if (isLongRangeNodeId(endpoint.nodeId)) { // Except the lifeline back to the host - if (group !== 1 || destination.nodeId !== applHost.ownNodeId) { + if (group !== 1 || destination.nodeId !== ctx.ownNodeId) { return AssociationCheckResult.Forbidden_SourceIsLongRange; } } // The following checks don't apply to Lifeline associations - if (destination.nodeId === applHost.ownNodeId) { + if (destination.nodeId === ctx.ownNodeId) { return AssociationCheckResult.OK; } @@ -160,7 +178,7 @@ export function checkAssociation( // A controlling node MUST NOT associate Node A to a Node B destination // if Node A was not granted Node B’s highest Security Class. - const sourceNode = endpoint.getNodeUnsafe()!; + const sourceNode = ctx.getNode(endpoint.nodeId)!; let securityClassMustMatch: boolean; if (destination.endpoint == undefined) { // "normal" association @@ -209,7 +227,7 @@ export function checkAssociation( } const groupCommandList = AssociationGroupInfoCC.getIssuedCommandsCached( - applHost, + ctx, endpoint, group, ); @@ -237,8 +255,8 @@ export function checkAssociation( } export function getAssociationGroups( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & GetDeviceConfig, + endpoint: EndpointId & SupportsCC, ): ReadonlyMap { // Check whether we have multi channel support or not let assocInstance: typeof AssociationCC; @@ -257,13 +275,13 @@ export function getAssociationGroups( mcInstance = MultiChannelAssociationCC; } - const assocGroupCount = - assocInstance.getGroupCountCached(applHost, endpoint) ?? 0; - const mcGroupCount = mcInstance?.getGroupCountCached(applHost, endpoint) + const assocGroupCount = assocInstance.getGroupCountCached(ctx, endpoint) + ?? 0; + const mcGroupCount = mcInstance?.getGroupCountCached(ctx, endpoint) ?? 0; const groupCount = Math.max(assocGroupCount, mcGroupCount); - const deviceConfig = applHost.getDeviceConfig?.(endpoint.nodeId); + const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId); const ret = new Map(); @@ -280,7 +298,7 @@ export function getAssociationGroups( maxNodes: (multiChannel ? mcInstance! : assocInstance).getMaxNodesCached( - applHost, + ctx, endpoint, group, ) || 1, @@ -291,7 +309,7 @@ export function getAssociationGroups( assocConfig?.label // the ones reported by AGI are sometimes pretty bad ?? agiInstance.getGroupNameCached( - applHost, + ctx, endpoint, group, ) @@ -299,12 +317,12 @@ export function getAssociationGroups( ?? `Unnamed group ${group}`, multiChannel, profile: agiInstance.getGroupProfileCached( - applHost, + ctx, endpoint, group, ), issuedCommands: agiInstance.getIssuedCommandsCached( - applHost, + ctx, endpoint, group, ), @@ -322,7 +340,7 @@ export function getAssociationGroups( maxNodes: (multiChannel ? mcInstance! : assocInstance).getMaxNodesCached( - applHost, + ctx, endpoint, group, ) @@ -338,21 +356,26 @@ export function getAssociationGroups( } export function getAllAssociationGroups( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB & GetDeviceConfig, + node: NodeId & GetAllEndpoints, ): ReadonlyMap> { const ret = new Map>(); for (const endpoint of node.getAllEndpoints()) { if (endpoint.supportsCC(CommandClasses.Association)) { - ret.set(endpoint.index, getAssociationGroups(applHost, endpoint)); + ret.set(endpoint.index, getAssociationGroups(ctx, endpoint)); } } return ret; } export async function addAssociations( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: CCAPIHost< + & CCAPINode + & SupportsCC + & GetEndpoint + & QuerySecurityClasses + >, + endpoint: EndpointId & SupportsCC & ControlsCC, group: number, destinations: AssociationAddress[], ): Promise { @@ -406,9 +429,9 @@ export async function addAssociations( ); } - const assocGroupCount = - assocInstance?.getGroupCountCached(applHost, endpoint) ?? 0; - const mcGroupCount = mcInstance?.getGroupCountCached(applHost, endpoint) + const assocGroupCount = assocInstance?.getGroupCountCached(ctx, endpoint) + ?? 0; + const mcGroupCount = mcInstance?.getGroupCountCached(ctx, endpoint) ?? 0; const groupCount = Math.max(assocGroupCount, mcGroupCount); if (group > groupCount) { @@ -418,7 +441,7 @@ export async function addAssociations( ); } - const deviceConfig = applHost.getDeviceConfig?.(endpoint.nodeId); + const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId); const groupIsMultiChannel = !!mcInstance && group <= mcGroupCount @@ -429,7 +452,7 @@ export async function addAssociations( const disallowedAssociations = destinations.map( (a) => ({ ...a, - checkResult: checkAssociation(applHost, endpoint, group, a), + checkResult: checkAssociation(ctx, endpoint, group, a), }), ).filter(({ checkResult }) => checkResult !== AssociationCheckResult.OK @@ -459,7 +482,7 @@ export async function addAssociations( // And add them const api = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, endpoint, ); await api.addDestinations({ @@ -482,7 +505,7 @@ export async function addAssociations( const disallowedAssociations = destinations.map( (a) => ({ ...a, - checkResult: checkAssociation(applHost, endpoint, group, a), + checkResult: checkAssociation(ctx, endpoint, group, a), }), ).filter(({ checkResult }) => checkResult !== AssociationCheckResult.OK @@ -511,7 +534,7 @@ export async function addAssociations( const api = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ); await api.addNodeIds(group, ...destinations.map((a) => a.nodeId)); @@ -521,8 +544,8 @@ export async function addAssociations( } export async function removeAssociations( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: CCAPIHost, + endpoint: EndpointId & SupportsCC & ControlsCC, group: number, destinations: AssociationAddress[], ): Promise { @@ -554,7 +577,7 @@ export async function removeAssociations( // and the node supports multi channel associations if (endpoint.supportsCC(CommandClasses["Multi Channel Association"])) { mcInstance = MultiChannelAssociationCC; - if (group <= mcInstance.getGroupCountCached(applHost, endpoint)) { + if (group <= mcInstance.getGroupCountCached(ctx, endpoint)) { groupExistsAsMultiChannel = true; } } else if (endpointAssociations.length > 0) { @@ -568,7 +591,7 @@ export async function removeAssociations( // or as a multi channel association if (endpoint.supportsCC(CommandClasses.Association)) { assocInstance = AssociationCC; - if (group <= assocInstance.getGroupCountCached(applHost, endpoint)) { + if (group <= assocInstance.getGroupCountCached(ctx, endpoint)) { groupExistsAsNodeAssociation = true; } } @@ -603,7 +626,7 @@ export async function removeAssociations( ) { const api = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ); await api.removeNodeIds({ @@ -617,7 +640,7 @@ export async function removeAssociations( if (mcInstance && groupExistsAsMultiChannel) { const api = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, endpoint, ); await api.removeDestinations({ @@ -631,12 +654,11 @@ export async function removeAssociations( } export function getLifelineGroupIds( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: GetValueDB & GetDeviceConfig, + endpoint: EndpointId & SupportsCC, ): number[] { // For now only support this for the root endpoint - i.e. node if (endpoint.index > 0) return []; - const node = endpoint as IZWaveNode; // Some nodes define multiple lifeline groups, so we need to assign us to // all of them @@ -649,7 +671,7 @@ export function getLifelineGroupIds( // We have a device config file that tells us which (additional) association to assign let associations: ReadonlyMap | undefined; - const deviceConfig = applHost.getDeviceConfig?.(node.id); + const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId); if (endpoint.index === 0) { // The root endpoint's associations may be configured separately or as part of "endpoints" associations = deviceConfig?.associations @@ -673,14 +695,19 @@ export function getLifelineGroupIds( } export async function configureLifelineAssociations( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: CCAPIHost< + & CCAPINode + & SupportsCC + & ControlsCC + & GetAllEndpoints + >, + endpoint: EndpointId & SupportsCC & ControlsCC, ): Promise { // Assign the controller to all lifeline groups - const ownNodeId = applHost.ownNodeId; - const node = endpoint.getNodeUnsafe()!; - const valueDB = applHost.getValueDB(node.id); - const deviceConfig = applHost.getDeviceConfig?.(node.id); + const ownNodeId = ctx.ownNodeId; + const node = ctx.getNodeOrThrow(endpoint.nodeId); + const valueDB = ctx.getValueDB(node.id); + const deviceConfig = ctx.getDeviceConfig?.(node.id); // We check if a node supports Multi Channel CC before creating Multi Channel Lifeline Associations (#1109) const nodeSupportsMultiChannel = node.supportsCC( @@ -690,7 +717,7 @@ export async function configureLifelineAssociations( let assocInstance: typeof AssociationCC | undefined; const assocAPI = CCAPI.create( CommandClasses.Association, - applHost, + ctx, endpoint, ); if (endpoint.supportsCC(CommandClasses.Association)) { @@ -701,15 +728,15 @@ export async function configureLifelineAssociations( let mcGroupCount = 0; const mcAPI = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, endpoint, ); if (endpoint.supportsCC(CommandClasses["Multi Channel Association"])) { mcInstance = MultiChannelAssociationCC; - mcGroupCount = mcInstance.getGroupCountCached(applHost, endpoint) ?? 0; + mcGroupCount = mcInstance.getGroupCountCached(ctx, endpoint) ?? 0; } - const lifelineGroups = getLifelineGroupIds(applHost, node); + const lifelineGroups = getLifelineGroupIds(ctx, node); if (lifelineGroups.length === 0) { // We can look for the General Lifeline AGI profile as a last resort if ( @@ -717,7 +744,7 @@ export async function configureLifelineAssociations( ) { const agiAPI = CCAPI.create( CommandClasses["Association Group Information"], - applHost, + ctx, endpoint, ); @@ -735,7 +762,7 @@ export async function configureLifelineAssociations( } if (lifelineGroups.length === 0) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: "No information about Lifeline associations, cannot assign ourselves!", @@ -749,7 +776,7 @@ export async function configureLifelineAssociations( return; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Checking/assigning lifeline groups: ${ lifelineGroups.join( @@ -796,7 +823,7 @@ supports multi channel associations: ${!!mcInstance}`, } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Configuring lifeline group #${group}: group supports multi channel: ${groupSupportsMultiChannelAssociation} @@ -807,15 +834,15 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, // Figure out which associations exist and may need to be removed const isAssignedAsNodeAssociation = ( - endpoint: IZWaveEndpoint, + endpoint: EndpointId, ): boolean => { if (groupSupportsMultiChannelAssociation && mcInstance) { if ( // Only consider a group if it doesn't share its associations with the root endpoint - mcInstance.getMaxNodesCached(applHost, endpoint, group) + mcInstance.getMaxNodesCached(ctx, endpoint, group) > 0 && !!mcInstance - .getAllDestinationsCached(applHost, endpoint) + .getAllDestinationsCached(ctx, endpoint) .get(group) ?.some( (addr) => @@ -829,10 +856,10 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, if (assocInstance) { if ( // Only consider a group if it doesn't share its associations with the root endpoint - assocInstance.getMaxNodesCached(applHost, endpoint, group) + assocInstance.getMaxNodesCached(ctx, endpoint, group) > 0 && !!assocInstance - .getAllDestinationsCached(applHost, endpoint) + .getAllDestinationsCached(ctx, endpoint) .get(group) ?.some((addr) => addr.nodeId === ownNodeId) ) { @@ -844,15 +871,15 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, }; const isAssignedAsEndpointAssociation = ( - endpoint: IZWaveEndpoint, + endpoint: EndpointId, ): boolean => { if (mcInstance) { if ( // Only consider a group if it doesn't share its associations with the root endpoint - mcInstance.getMaxNodesCached(applHost, endpoint, group) + mcInstance.getMaxNodesCached(ctx, endpoint, group) > 0 && mcInstance - .getAllDestinationsCached(applHost, endpoint) + .getAllDestinationsCached(ctx, endpoint) .get(group) ?.some( (addr) => @@ -869,7 +896,7 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, // If the node was used with other controller software, there might be // invalid lifeline associations which cause reporting problems const invalidEndpointAssociations: EndpointAddress[] = mcInstance - ?.getAllDestinationsCached(applHost, endpoint) + ?.getAllDestinationsCached(ctx, endpoint) .get(group) ?.filter( (addr): addr is AssociationAddress & EndpointAddress => @@ -884,7 +911,7 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, && mcAPI.isSupported() && groupSupportsMultiChannelAssociation ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Found invalid lifeline associations in group #${group}, removing them...`, @@ -916,7 +943,7 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, if (isAssignedAsNodeAssociation(endpoint)) { // We already have the correct association hasLifeline = true; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} is already assigned with a node association`, @@ -925,11 +952,11 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, } else if ( assocAPI.isSupported() // Some endpoint groups don't support having any destinations because they are shared with the root - && assocInstance!.getMaxNodesCached(applHost, endpoint, group) + && assocInstance!.getMaxNodesCached(ctx, endpoint, group) > 0 ) { // We can use a node association, but first remove any possible endpoint associations - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a node association via Association CC...`, @@ -953,14 +980,14 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, hasLifeline = !!groupReport?.nodeIds.includes(ownNodeId); if (hasLifeline) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} was assigned with a node association via Association CC`, direction: "none", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a node association via Association CC did not work`, @@ -973,10 +1000,10 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, if ( !hasLifeline && mcAPI.isSupported() - && mcInstance!.getMaxNodesCached(applHost, endpoint, group) > 0 + && mcInstance!.getMaxNodesCached(ctx, endpoint, group) > 0 ) { // We can use a node association, but first remove any possible endpoint associations - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a node association via Multi Channel Association CC...`, @@ -998,14 +1025,14 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, hasLifeline = !!groupReport?.nodeIds.includes(ownNodeId); if (hasLifeline) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} was assigned with a node association via Multi Channel Association CC`, direction: "none", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a node association via Multi Channel Association CC did not work`, @@ -1021,7 +1048,7 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, if (isAssignedAsEndpointAssociation(endpoint)) { // We already have the correct association hasLifeline = true; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} is already assigned with an endpoint association`, @@ -1030,10 +1057,10 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, } else if ( mcAPI.isSupported() && mcAPI.version >= 3 - && mcInstance!.getMaxNodesCached(applHost, endpoint, group) > 0 + && mcInstance!.getMaxNodesCached(ctx, endpoint, group) > 0 ) { // We can use a multi channel association, but first remove any possible node associations - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a multi channel association...`, @@ -1067,14 +1094,14 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, ); if (hasLifeline) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} was assigned with a multi channel association`, direction: "none", }); } else { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a multi channel association did not work`, @@ -1099,7 +1126,7 @@ must use endpoint association: ${mustUseMultiChannelAssociation}`, const rootMustUseNodeAssociation = !nodeSupportsMultiChannel || rootAssocConfig?.multiChannel === false; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Checking root device for fallback assignment of lifeline group #${group}: @@ -1112,7 +1139,7 @@ must use node association: ${rootMustUseNodeAssociation}`, if (isAssignedAsEndpointAssociation(node)) { // We already have the correct association hasLifeline = true; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Lifeline group #${group} is already assigned with a multi channel association on the root device`, @@ -1121,16 +1148,16 @@ must use node association: ${rootMustUseNodeAssociation}`, } else { const rootMCAPI = CCAPI.create( CommandClasses["Multi Channel Association"], - applHost, + ctx, node, ); const rootAssocAPI = CCAPI.create( CommandClasses.Association, - applHost, + ctx, node, ); if (rootMCAPI.isSupported()) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Assigning lifeline group #${group} with a multi channel association on the root device...`, @@ -1168,7 +1195,7 @@ must use node association: ${rootMustUseNodeAssociation}`, } if (!hasLifeline) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `All attempts to assign lifeline group #${group} failed, skipping...`, @@ -1186,12 +1213,18 @@ must use node association: ${rootMustUseNodeAssociation}`, } export async function assignLifelineIssueingCommand( - applHost: ZWaveApplicationHost, - endpoint: IZWaveEndpoint, + ctx: CCAPIHost< + & CCAPINode + & SupportsCC + & ControlsCC + & GetEndpoint + & QuerySecurityClasses + >, + endpoint: EndpointId, ccId: CommandClasses, ccCommand: number, ): Promise { - const node = endpoint.getNodeUnsafe()!; + const node = ctx.getNodeOrThrow(endpoint.nodeId); if ( node.supportsCC(CommandClasses["Association Group Information"]) && (node.supportsCC(CommandClasses.Association) @@ -1199,7 +1232,7 @@ export async function assignLifelineIssueingCommand( ) { const groupsIssueingNotifications = AssociationGroupInfoCC .findGroupsForIssuedCommand( - applHost, + ctx, node, ccId, ccCommand, @@ -1207,15 +1240,15 @@ export async function assignLifelineIssueingCommand( if (groupsIssueingNotifications.length > 0) { // We always grab the first group - usually it should be the lifeline const groupId = groupsIssueingNotifications[0]; - const existingAssociations = - getAssociations(applHost, node).get(groupId) ?? []; + const existingAssociations = getAssociations(ctx, node).get(groupId) + ?? []; if ( !existingAssociations.some( - (a) => a.nodeId === applHost.ownNodeId, + (a) => a.nodeId === ctx.ownNodeId, ) ) { - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: endpoint.index, message: `Configuring associations to receive ${ getCCName( @@ -1224,8 +1257,8 @@ export async function assignLifelineIssueingCommand( } commands...`, direction: "outbound", }); - await addAssociations(applHost, node, groupId, [ - { nodeId: applHost.ownNodeId }, + await addAssociations(ctx, node, groupId, [ + { nodeId: ctx.ownNodeId }, ]); } } @@ -1233,8 +1266,8 @@ export async function assignLifelineIssueingCommand( } export function doesAnyLifelineSendActuatorOrSensorReports( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB & GetDeviceConfig, + node: NodeId & SupportsCC, ): MaybeNotKnown { // No association support means no unsolicited reports if ( @@ -1250,13 +1283,13 @@ export function doesAnyLifelineSendActuatorOrSensorReports( } // Lifeline group IDs include the ones we added via a config file, so they may not be considered true lifelines - const lifelineGroupIds = getLifelineGroupIds(applHost, node); + const lifelineGroupIds = getLifelineGroupIds(ctx, node); // If any potential lifeline group has the "General: Lifeline" profile, the node MUST send unsolicited reports that way if ( lifelineGroupIds.some( (id) => AssociationGroupInfoCC.getGroupProfileCached( - applHost, + ctx, node, id, ) === AssociationGroupInfoProfile["General: Lifeline"], @@ -1268,7 +1301,7 @@ export function doesAnyLifelineSendActuatorOrSensorReports( // Otherwise check if any of the groups sends any actuator or sensor commands. We'll assume that those are reports for (const groupId of lifelineGroupIds) { const issuedCommands = AssociationGroupInfoCC.getIssuedCommandsCached( - applHost, + ctx, node, groupId, ); diff --git a/packages/core/src/abstractions/ICommandClass.ts b/packages/core/src/abstractions/ICommandClass.ts deleted file mode 100644 index 392c485d6d1e..000000000000 --- a/packages/core/src/abstractions/ICommandClass.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { CommandClasses } from "../capabilities/CommandClasses"; -import type { - MulticastDestination, - NODE_ID_BROADCAST, - NODE_ID_BROADCAST_LR, -} from "../consts"; - -/** A basic abstraction of a Z-Wave Command Class providing access to the relevant functionality */ -export interface ICommandClass { - ccId: CommandClasses; - ccCommand?: number; - - serialize(): Buffer; - nodeId: number | MulticastDestination; - expectsCCResponse(): boolean; - isExpectedCCResponse(received: ICommandClass): boolean; -} - -export type SinglecastCC = T & { - nodeId: number; -}; - -export type MulticastCC = T & { - nodeId: MulticastDestination; -}; - -export type BroadcastCC = T & { - nodeId: typeof NODE_ID_BROADCAST | typeof NODE_ID_BROADCAST_LR; -}; diff --git a/packages/core/src/abstractions/IZWaveEndpoint.ts b/packages/core/src/abstractions/IZWaveEndpoint.ts deleted file mode 100644 index 46e2c5ee20aa..000000000000 --- a/packages/core/src/abstractions/IZWaveEndpoint.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { - CommandClassInfo, - CommandClasses, -} from "../capabilities/CommandClasses"; -import type { MulticastDestination } from "../consts"; -import type { IVirtualNode, IZWaveNode } from "./IZWaveNode"; - -/** A basic abstraction of a Z-Wave endpoint providing access to the relevant functionality */ -export interface IZWaveEndpoint { - readonly nodeId: number; - readonly index: number; - readonly virtual: false; - getCCVersion(cc: CommandClasses): number; - addCC(cc: CommandClasses, info: Partial): void; - removeCC(cc: CommandClasses): void; - getCCs(): Iterable<[ccId: CommandClasses, info: CommandClassInfo]>; - supportsCC(cc: CommandClasses): boolean; - controlsCC(cc: CommandClasses): boolean; - isCCSecure(cc: CommandClasses): boolean; - getNodeUnsafe(): IZWaveNode | undefined; -} - -/** A basic abstraction of an endpoint of a virtual node (multicast or broadcast) providing access to the relevant functionality */ -export interface IVirtualEndpoint { - readonly nodeId: number | MulticastDestination; - readonly node: IVirtualNode; - readonly index: number; - readonly virtual: true; - getCCVersion(cc: CommandClasses): number; - supportsCC(cc: CommandClasses): boolean; -} diff --git a/packages/core/src/abstractions/IZWaveNode.ts b/packages/core/src/abstractions/IZWaveNode.ts deleted file mode 100644 index b24420db58b5..000000000000 --- a/packages/core/src/abstractions/IZWaveNode.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { FLiRS } from "../capabilities/NodeInfo"; -import type { InterviewStage, NodeStatus } from "../consts"; -import type { SecurityClassOwner } from "../security/SecurityClass"; -import { type MaybeNotKnown } from "../values/Primitive"; -import type { IVirtualEndpoint, IZWaveEndpoint } from "./IZWaveEndpoint"; - -/** A basic abstraction of a Z-Wave node providing access to the relevant functionality */ -export interface IZWaveNode extends IZWaveEndpoint, SecurityClassOwner { - readonly id: number; - isListening: MaybeNotKnown; - isFrequentListening: MaybeNotKnown; - readonly canSleep: MaybeNotKnown; - readonly status: NodeStatus; - interviewStage: InterviewStage; - - getEndpoint(index: 0): IZWaveEndpoint; - getEndpoint(index: number): IZWaveEndpoint | undefined; - getEndpointOrThrow(index: number): IZWaveEndpoint; - getAllEndpoints(): IZWaveEndpoint[]; - readonly isSecure: MaybeNotKnown; -} - -/** A basic abstraction of a virtual node (multicast or broadcast) providing access to the relevant functionality */ -export interface IVirtualNode extends IVirtualEndpoint { - readonly id: number | undefined; - readonly physicalNodes: readonly IZWaveNode[]; - - getEndpoint(index: 0): IVirtualEndpoint; - getEndpoint(index: number): IVirtualEndpoint | undefined; - getEndpointOrThrow(index: number): IVirtualEndpoint; -} diff --git a/packages/core/src/consts/Transmission.ts b/packages/core/src/consts/Transmission.ts index fdab874520a1..006dab8e9cab 100644 --- a/packages/core/src/consts/Transmission.ts +++ b/packages/core/src/consts/Transmission.ts @@ -1,8 +1,8 @@ import { num2hex } from "@zwave-js/shared/safe"; import { isObject } from "alcalzone-shared/typeguards"; -import type { ICommandClass } from "../abstractions/ICommandClass"; import type { ProtocolDataRate } from "../capabilities/Protocols"; import { type SecurityClass } from "../security/SecurityClass"; +import type { CCId } from "../traits/CommandClasses"; import { Duration } from "../values/Duration"; /** The priority of messages, sorted from high (0) to low (>0) */ @@ -297,7 +297,7 @@ export type SendCommandOptions = reportTimeoutMs?: number; }; -export type SendCommandReturnType = +export type SendCommandReturnType = undefined extends TResponse ? SupervisionResult | undefined : TResponse | undefined; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6afee8722c97..4fe19cbc112e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,4 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ -export * from "./abstractions/ICommandClass"; -export * from "./abstractions/IZWaveEndpoint"; -export * from "./abstractions/IZWaveNode"; export * from "./capabilities/CommandClasses"; export * from "./capabilities/ControllerCapabilities"; export * from "./capabilities/LibraryTypes"; @@ -30,6 +27,10 @@ export * from "./security/crypto"; export * from "./security/ctr_drbg"; export * from "./security/shared_safe"; export * from "./test/assertZWaveError"; +export * from "./traits/CommandClasses"; +export * from "./traits/Endpoints"; +export * from "./traits/Nodes"; +export * from "./traits/SecurityManagers"; export * from "./util/_Types"; export * from "./util/config"; export * from "./util/crc"; diff --git a/packages/core/src/index_safe.ts b/packages/core/src/index_safe.ts index c241edf8958f..7c15660e1ae8 100644 --- a/packages/core/src/index_safe.ts +++ b/packages/core/src/index_safe.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ /* @forbiddenImports external */ -export * from "./abstractions/ICommandClass"; -export * from "./abstractions/IZWaveEndpoint"; -export * from "./abstractions/IZWaveNode"; export * from "./capabilities/CommandClasses"; export * from "./capabilities/ControllerCapabilities"; export * from "./capabilities/LibraryTypes"; @@ -24,6 +21,11 @@ export * from "./registries/Sensors"; export * from "./security/DSK"; export * from "./security/SecurityClass"; export * from "./security/shared_safe"; +export * from "./traits/CommandClasses"; +export * from "./traits/Endpoints"; +export * from "./traits/Endpoints"; +export * from "./traits/Nodes"; +export * from "./traits/SecurityManagers"; export * from "./util/_Types"; export * from "./util/config"; export * from "./util/crc"; diff --git a/packages/core/src/security/SecurityClass.ts b/packages/core/src/security/SecurityClass.ts index ed06e521a877..cc4e46795593 100644 --- a/packages/core/src/security/SecurityClass.ts +++ b/packages/core/src/security/SecurityClass.ts @@ -50,10 +50,20 @@ export const securityClassOrder = [ SecurityClass.S0_Legacy, ] as const; -export interface SecurityClassOwner { - readonly id: number; - getHighestSecurityClass(): MaybeNotKnown; +/** Allows querying the security classes of a node */ +export interface QuerySecurityClasses { + /** Whether the node was granted at least one security class */ + readonly isSecure: MaybeNotKnown; + + /** Returns whether a node was granted the given security class */ hasSecurityClass(securityClass: SecurityClass): MaybeNotKnown; + + /** Returns the highest security class this node was granted or `undefined` if that information isn't known yet */ + getHighestSecurityClass(): MaybeNotKnown; +} + +/** Allows modifying the security classes of a node */ +export interface SetSecurityClass { setSecurityClass(securityClass: SecurityClass, granted: boolean): void; } diff --git a/packages/core/src/traits/CommandClasses.ts b/packages/core/src/traits/CommandClasses.ts new file mode 100644 index 000000000000..759567a2d7dc --- /dev/null +++ b/packages/core/src/traits/CommandClasses.ts @@ -0,0 +1,56 @@ +import type { + CommandClassInfo, + CommandClasses, +} from "../capabilities/CommandClasses"; +import type { + MulticastDestination, + NODE_ID_BROADCAST, + NODE_ID_BROADCAST_LR, +} from "../consts"; + +/** A basic abstraction of a Z-Wave Command Class providing access to the relevant functionality */ +export interface CCId { + nodeId: number | MulticastDestination; + endpointIndex?: number; + ccId: CommandClasses; + ccCommand?: number; +} + +export type SinglecastCC = T & { + nodeId: number; +}; + +export type MulticastCC = T & { + nodeId: MulticastDestination; +}; + +export type BroadcastCC = T & { + nodeId: typeof NODE_ID_BROADCAST | typeof NODE_ID_BROADCAST_LR; +}; + +/** Allows querying if a CC is supported and in which version */ +export interface SupportsCC { + supportsCC(cc: CommandClasses): boolean; + getCCVersion(cc: CommandClasses): number; +} + +/** Allows querying if a CC is controlled */ +export interface ControlsCC { + controlsCC(cc: CommandClasses): boolean; +} + +/** Allows querying if a CC is supported or controlled only securely */ +export interface IsCCSecure { + isCCSecure(cc: CommandClasses): boolean; +} + +/** Allows querying all implemented CCs and their information */ +export interface GetCCs { + getCCs(): Iterable<[ccId: CommandClasses, info: CommandClassInfo]>; +} + +/** Allows modifying the list of supported/controlled CCs */ +export interface ModifyCCs { + addCC(cc: CommandClasses, info: Partial): void; + removeCC(cc: CommandClasses): void; +} diff --git a/packages/core/src/traits/Endpoints.ts b/packages/core/src/traits/Endpoints.ts new file mode 100644 index 000000000000..cfc505f789e7 --- /dev/null +++ b/packages/core/src/traits/Endpoints.ts @@ -0,0 +1,15 @@ +import { type MulticastDestination } from "../consts/Transmission"; + +/** Identifies an endpoint */ +export interface EndpointId { + readonly virtual: false; + readonly nodeId: number; + readonly index: number; +} + +/** Identifies a virtual endpoint */ +export interface VirtualEndpointId { + readonly virtual: true; + readonly nodeId: number | MulticastDestination; + readonly index: number; +} diff --git a/packages/core/src/traits/Nodes.ts b/packages/core/src/traits/Nodes.ts new file mode 100644 index 000000000000..80b6fbbac7c3 --- /dev/null +++ b/packages/core/src/traits/Nodes.ts @@ -0,0 +1,51 @@ +import { type FLiRS } from "../capabilities/NodeInfo"; +import { type NodeStatus } from "../consts/NodeStatus"; +import { type MaybeNotKnown } from "../values/Primitive"; +import { type EndpointId, type VirtualEndpointId } from "./Endpoints"; + +/** Identifies a node */ +export interface NodeId extends EndpointId { + readonly id: number; + // // FIXME: GH#7261 this should have type 0 + // readonly index: number; +} + +export interface VirtualNodeId extends VirtualEndpointId { + readonly id: number | undefined; +} + +/** Allows accessing a specific endpoint */ +export interface GetEndpoint { + getEndpoint(index: 0): T; + getEndpoint(index: number): T | undefined; + getEndpointOrThrow(index: number): T; +} + +/** Allows accessing all endpoints */ +export interface GetAllEndpoints { + getAllEndpoints(): T[]; +} + +/** Allows querying whether a node is a listening, FLiRS or sleeping device */ +export interface ListenBehavior { + /** Whether this node is always listening or not */ + readonly isListening: MaybeNotKnown; + + /** Indicates the wakeup interval if this node is a FLiRS node. `false` if it isn't. */ + readonly isFrequentListening: MaybeNotKnown; + + /** Whether this node can sleep */ + readonly canSleep: MaybeNotKnown; +} + +/** Allows querying whether a node's status */ +export interface QueryNodeStatus { + /** + * Which status the node is believed to be in + */ + readonly status: NodeStatus; +} + +export interface PhysicalNodes { + readonly physicalNodes: readonly T[]; +} diff --git a/packages/core/src/traits/SecurityManagers.ts b/packages/core/src/traits/SecurityManagers.ts new file mode 100644 index 000000000000..c366240633e5 --- /dev/null +++ b/packages/core/src/traits/SecurityManagers.ts @@ -0,0 +1,12 @@ +import { type SecurityManager } from "../security/Manager"; +import { type SecurityManager2 } from "../security/Manager2"; + +/** Allows accessing the security manager instances */ +export interface SecurityManagers { + /** Management of Security S0 keys and nonces */ + securityManager: SecurityManager | undefined; + /** Management of Security S2 keys and nonces (Z-Wave Classic) */ + securityManager2: SecurityManager2 | undefined; + /** Management of Security S2 keys and nonces (Z-Wave Long Range) */ + securityManagerLR: SecurityManager2 | undefined; +} diff --git a/packages/host/src/ZWaveHost.ts b/packages/host/src/ZWaveHost.ts index 23de183ef77e..ce10e851ffd9 100644 --- a/packages/host/src/ZWaveHost.ts +++ b/packages/host/src/ZWaveHost.ts @@ -1,50 +1,44 @@ -import type { ConfigManager, DeviceConfig } from "@zwave-js/config"; +import type { DeviceConfig } from "@zwave-js/config"; import type { + CCId, CommandClasses, ControllerLogger, - ICommandClass, - IZWaveNode, + FrameType, MaybeNotKnown, - NodeIDType, + NodeId, SecurityClass, - SecurityManager, - SecurityManager2, + SecurityManagers, SendCommandOptions, SendCommandReturnType, ValueDB, ValueID, } from "@zwave-js/core"; -import type { ReadonlyThrowingMap } from "@zwave-js/shared"; import type { ZWaveHostOptions } from "./ZWaveHostOptions"; -/** Host application abstractions to be used in Serial API and CC implementations */ -export interface ZWaveHost { +/** Allows querying the home ID and node ID of the host */ +export interface HostIDs { /** The ID of this node in the current network */ ownNodeId: number; /** The Home ID of the current network */ homeId: number; +} - /** How many bytes a node ID occupies in serial API commands */ - readonly nodeIdType?: NodeIDType; - - /** Management of Security S0 keys and nonces */ - securityManager: SecurityManager | undefined; - /** Management of Security S2 keys and nonces (Z-Wave Classic) */ - securityManager2: SecurityManager2 | undefined; - /** Management of Security S2 keys and nonces (Z-Wave Long Range) */ - securityManagerLR: SecurityManager2 | undefined; - +// FIXME: This should not be needed. Instead have the driver set callback IDs during sendMessage +/** Allows generating a new callback ID */ +export interface GetNextCallbackId { /** - * Retrieves the maximum version of a command class that can be used to communicate with a node. - * Returns 1 if the node claims that it does not support a CC. - * Throws if the CC is not implemented in this library yet. + * Returns the next callback ID. Callback IDs are used to correlate requests + * to the controller/nodes with its response */ - getSafeCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): number; + getNextCallbackId(): number; +} +/** Allows querying device configuration for a node */ +export interface GetDeviceConfig { + getDeviceConfig(nodeId: number): DeviceConfig | undefined; +} + +export interface GetSupportedCCVersion { /** * Retrieves the maximum version of a command class the given node/endpoint has reported support for. * Returns 0 when the CC is not supported or that information is not known yet. @@ -54,15 +48,30 @@ export interface ZWaveHost { nodeId: number, endpointIndex?: number, ): number; +} +export interface GetSafeCCVersion { /** - * Determines whether a CC must be secure for a given node and endpoint. + * Retrieves the maximum version of a command class that can be used to communicate with a node. + * Returns 1 if the node claims that it does not support a CC. + * Returns `undefined` for CCs that are not implemented in this library yet. */ - isCCSecure( + getSafeCCVersion( cc: CommandClasses, nodeId: number, endpointIndex?: number, - ): boolean; + ): number | undefined; +} + +/** Additional context needed for deserializing CCs */ +export interface CCParsingContext + extends Readonly, GetDeviceConfig, HostIDs +{ + sourceNodeId: number; + __internalIsMockNode?: boolean; + + /** If known, the frame type of the containing message */ + frameType?: FrameType; getHighestSecurityClass(nodeId: number): MaybeNotKnown; @@ -76,25 +85,33 @@ export interface ZWaveHost { securityClass: SecurityClass, granted: boolean, ): void; +} - /** - * Returns the next callback ID. Callback IDs are used to correlate requests - * to the controller/nodes with its response - */ - getNextCallbackId(): number; - - /** - * Returns the next session ID for supervised communication - */ - getNextSupervisionSessionId(nodeId: number): number; +/** Additional context needed for serializing CCs */ +// FIXME: Lot of duplication between the CC and message contexts +export interface CCEncodingContext + extends + Readonly, + GetDeviceConfig, + HostIDs, + GetSupportedCCVersion +{ + getHighestSecurityClass(nodeId: number): MaybeNotKnown; - getDeviceConfig?: (nodeId: number) => DeviceConfig | undefined; + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown; - __internalIsMockNode?: boolean; + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void; } /** Host application abstractions that provide support for reading and writing values to a database */ -export interface ZWaveValueHost { +export interface GetValueDB { /** Returns the value DB which belongs to the node with the given ID, or throws if the Value DB cannot be accessed */ getValueDB(nodeId: number): ValueDB; @@ -102,32 +119,50 @@ export interface ZWaveValueHost { tryGetValueDB(nodeId: number): ValueDB | undefined; } -/** A more featureful version of the ZWaveHost interface, which is meant to be used on the controller application side. */ -export interface ZWaveApplicationHost extends ZWaveValueHost, ZWaveHost { - /** Gives access to the configuration files */ - configManager: ConfigManager; - - options: ZWaveHostOptions; - - // TODO: There's probably a better fitting name for this now - controllerLog: ControllerLogger; +/** Allows accessing a specific node */ +export interface GetNode { + getNode(nodeId: number): T | undefined; + getNodeOrThrow(nodeId: number): T; +} - /** Readonly access to all node instances known to the host */ - nodes: ReadonlyThrowingMap; +/** Allows accessing all nodes */ +export interface GetAllNodes { + getAllNodes(): T[]; +} - /** Whether the node with the given ID is the controller */ - isControllerNode(nodeId: number): boolean; +/** Allows looking up Z-Wave manufacturers by manufacturer ID */ +export interface LookupManufacturer { + /** Looks up the name of the manufacturer with the given ID in the configuration DB */ + lookupManufacturer(manufacturerId: number): string | undefined; +} - sendCommand( - command: ICommandClass, +/** Allows sending commands to one or more nodes */ +export interface SendCommand { + sendCommand( + command: CCId, options?: SendCommandOptions, ): Promise>; +} + +/** Allows reading options to use for interviewing devices */ +export interface GetInterviewOptions { + getInterviewOptions(): ZWaveHostOptions["interview"]; +} + +/** Allows reading user preferences */ +export interface GetUserPreferences { + getUserPreferences(): ZWaveHostOptions["preferences"]; +} + +/** Allows reading user preferences */ +export interface GetCommunicationTimeouts { + getCommunicationTimeouts(): ZWaveHostOptions["timeouts"]; +} - waitForCommand( - predicate: (cc: ICommandClass) => boolean, - timeout: number, - ): Promise; +export type LogNode = Pick; +/** Allows scheduling a value refresh (poll) for a later time */ +export interface SchedulePoll { schedulePoll( nodeId: number, valueId: ValueID, diff --git a/packages/host/src/mocks.ts b/packages/host/src/mocks.ts index ca42be0f4850..b851a29271b9 100644 --- a/packages/host/src/mocks.ts +++ b/packages/host/src/mocks.ts @@ -1,90 +1,128 @@ -/* eslint-disable @typescript-eslint/require-await */ -import { ConfigManager } from "@zwave-js/config"; import { - type IZWaveNode, - MAX_SUPERVISION_SESSION_ID, - NodeIDType, + type ControlsCC, + type EndpointId, + type GetEndpoint, + type IsCCSecure, + type ListenBehavior, + type NodeId, + type QuerySecurityClasses, + type SetSecurityClass, + type SupportsCC, ValueDB, ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import { - type ThrowingMap, - createThrowingMap, - createWrappingCounter, -} from "@zwave-js/shared"; -import type { Overwrite } from "alcalzone-shared/types"; -import type { ZWaveApplicationHost, ZWaveHost } from "./ZWaveHost"; +import { createThrowingMap } from "@zwave-js/shared"; +import type { + GetAllNodes, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, + HostIDs, + LogNode, +} from "./ZWaveHost"; -export interface CreateTestingHostOptions { - homeId: ZWaveHost["homeId"]; - ownNodeId: ZWaveHost["ownNodeId"]; - getSafeCCVersion: ZWaveHost["getSafeCCVersion"]; - getSupportedCCVersion?: ZWaveHost["getSupportedCCVersion"]; -} +export interface CreateTestingHostOptions extends HostIDs, GetDeviceConfig {} + +export type BaseTestEndpoint = + & EndpointId + & SupportsCC + & ControlsCC + & IsCCSecure; -export type TestingHost = Overwrite< - Omit, - { nodes: ThrowingMap } ->; +export type BaseTestNode = + & BaseTestEndpoint + & NodeId + & ListenBehavior + & QuerySecurityClasses + & SetSecurityClass + & SupportsCC + & ControlsCC + & IsCCSecure + & GetEndpoint; + +export interface TestingHost + extends + HostIDs, + GetValueDB, + GetSupportedCCVersion, + GetAllNodes, + GetNode, + GetDeviceConfig, + LogNode +{ + setNode(nodeId: number, node: BaseTestNode): void; +} -/** Creates a {@link ZWaveApplicationHost} that can be used for testing */ +/** Creates a {@link TestingHost} that can be used instead of a real driver instance in tests */ export function createTestingHost( options: Partial = {}, ): TestingHost { const valuesStorage = new Map(); const metadataStorage = new Map(); const valueDBCache = new Map(); - const supervisionSessionIDs = new Map number>(); + const nodes = createThrowingMap((nodeId) => { + throw new ZWaveError( + `Node ${nodeId} was not found!`, + ZWaveErrorCodes.Controller_NodeNotFound, + ); + }); const ret: TestingHost = { homeId: options.homeId ?? 0x7e570001, ownNodeId: options.ownNodeId ?? 1, - nodeIdType: NodeIDType.Short, - isControllerNode: (nodeId) => nodeId === ret.ownNodeId, - securityManager: undefined, - securityManager2: undefined, - securityManagerLR: undefined, - getDeviceConfig: undefined, - controllerLog: new Proxy({} as any, { - get() { - return () => { - /* intentionally empty */ - }; - }, - }), - configManager: new ConfigManager(), - options: { - attempts: { - nodeInterview: 1, - // openSerialPort: 1, - sendData: 3, - controller: 3, - }, - timeouts: { - refreshValue: 5000, - refreshValueAfterTransition: 1000, - }, + getDeviceConfig: options.getDeviceConfig ?? (() => undefined), + // securityManager: undefined, + // securityManager2: undefined, + // securityManagerLR: undefined, + // getDeviceConfig: () => undefined, + // lookupManufacturer: () => undefined, + logNode: () => {}, + // options: { + // attempts: { + // nodeInterview: 1, + // // openSerialPort: 1, + // sendData: 3, + // controller: 3, + // }, + // timeouts: { + // refreshValue: 5000, + // refreshValueAfterTransition: 1000, + // }, + // }, + // getInterviewOptions() { + // return {}; + // }, + // getUserPreferences() { + // return undefined; + // }, + // getCommunicationTimeouts() { + // return { + // refreshValue: 5000, + // refreshValueAfterTransition: 1000, + // }; + // }, + getNode(nodeId) { + return nodes.get(nodeId); }, - nodes: createThrowingMap((nodeId) => { - throw new ZWaveError( - `Node ${nodeId} was not found!`, - ZWaveErrorCodes.Controller_NodeNotFound, - ); - }), - getSafeCCVersion: options.getSafeCCVersion ?? (() => 100), - getSupportedCCVersion: options.getSupportedCCVersion - ?? options.getSafeCCVersion - ?? (() => 100), - getNextCallbackId: createWrappingCounter(0xff), - getNextSupervisionSessionId: (nodeId) => { - if (!supervisionSessionIDs.has(nodeId)) { - supervisionSessionIDs.set( - nodeId, - createWrappingCounter(MAX_SUPERVISION_SESSION_ID, true), - ); - } - return supervisionSessionIDs.get(nodeId)!(); + getNodeOrThrow(nodeId) { + return nodes.getOrThrow(nodeId); + }, + getAllNodes() { + return [...nodes.values()]; + }, + setNode(nodeId, node) { + nodes.set(nodeId, node); + }, + // getSafeCCVersion: options.getSafeCCVersion ?? (() => 100), + getSupportedCCVersion: (cc, nodeId, endpoint) => { + return nodes.get(nodeId)?.getEndpoint(endpoint ?? 0)?.getCCVersion( + cc, + ) ?? 0; + // return options.getSupportedCCVersion?.(cc, nodeId, endpoint) + // ?? options.getSafeCCVersion?.(cc, nodeId, endpoint) + // ?? 100; }, getValueDB: (nodeId) => { if (!valueDBCache.has(nodeId)) { @@ -102,36 +140,24 @@ export function createTestingHost( tryGetValueDB: (nodeId) => { return ret.getValueDB(nodeId); }, - isCCSecure: (ccId, nodeId, endpointIndex = 0) => { - const node = ret.nodes.get(nodeId); - const endpoint = node?.getEndpoint(endpointIndex); - return ( - node?.isSecure !== false - && !!(endpoint ?? node)?.isCCSecure(ccId) - && !!(ret.securityManager || ret.securityManager2) - ); - }, - getHighestSecurityClass: (nodeId) => { - const node = ret.nodes.getOrThrow(nodeId); - return node.getHighestSecurityClass(); - }, - hasSecurityClass: (nodeId, securityClass) => { - const node = ret.nodes.getOrThrow(nodeId); - return node.hasSecurityClass(securityClass); - }, - setSecurityClass: (nodeId, securityClass, granted) => { - const node = ret.nodes.getOrThrow(nodeId); - node.setSecurityClass(securityClass, granted); - }, - sendCommand: async (_command, _options) => { - return undefined as any; - }, - waitForCommand: async (_predicate, _timeout) => { - return undefined as any; - }, - schedulePoll: (_nodeId, _valueId, _options) => { - return false; - }, + // getHighestSecurityClass: (nodeId) => { + // const node = nodes.getOrThrow(nodeId); + // return node.getHighestSecurityClass(); + // }, + // hasSecurityClass: (nodeId, securityClass) => { + // const node = nodes.getOrThrow(nodeId); + // return node.hasSecurityClass(securityClass); + // }, + // setSecurityClass: (nodeId, securityClass, granted) => { + // const node = nodes.getOrThrow(nodeId); + // node.setSecurityClass(securityClass, granted); + // }, + // sendCommand: async (_command, _options) => { + // return undefined as any; + // }, + // schedulePoll: (_nodeId, _valueId, _options) => { + // return false; + // }, }; return ret; } diff --git a/packages/maintenance/package.json b/packages/maintenance/package.json index a1ace8ddac09..cf08490610a1 100644 --- a/packages/maintenance/package.json +++ b/packages/maintenance/package.json @@ -42,6 +42,7 @@ "@dprint/typescript": "^0.93.0", "@types/fs-extra": "^11.0.4", "@types/globrex": "^0.1.4", + "@types/jscodeshift": "^0.12.0", "@types/node": "^18.19.55", "@types/yargs": "^17.0.33", "@zwave-js/core": "workspace:*", @@ -56,6 +57,7 @@ "execa": "^5.1.1", "fs-extra": "^11.2.0", "globrex": "^0.1.2", + "jscodeshift": "^17.0.0", "json5": "^2.2.3", "piscina": "^4.7.0", "reflect-metadata": "^0.2.2", diff --git a/packages/maintenance/src/codeshifts/moveImport.ts b/packages/maintenance/src/codeshifts/moveImport.ts new file mode 100644 index 000000000000..5bfc1da315ca --- /dev/null +++ b/packages/maintenance/src/codeshifts/moveImport.ts @@ -0,0 +1,104 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + { + name, + from, + to, + }: Options, +) => { + if (!name || !to) { + throw new Error("Both 'name' and 'to' are required options"); + } + + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const imp = root + .find( + j.ImportDeclaration, + from && { + source: { + type: "StringLiteral", + value: from, + }, + }, + ) + .find(j.ImportSpecifier, { + imported: { + name: name, + }, + }); + + if (imp.length !== 1) return file.source; + + const decl = imp.closest(j.ImportDeclaration); + + const isTypeOnly = (imp.at(0).nodes()[0] as any).importKind === "type" + || decl.at(0).nodes()[0].importKind === "type"; + + // Remove the found import from its parent + imp.remove(); + // And remove all empty imports + root + .find( + j.ImportDeclaration, + from && { + source: { + type: "StringLiteral", + value: from, + }, + }, + ).filter((path) => !path.value.specifiers?.length) + .remove(); + + // Try to find an existing import for the new specifier + const targetDecl = root + .find( + j.ImportDeclaration, + { + source: { + type: "StringLiteral", + value: to, + }, + ...(isTypeOnly ? { importKind: "type" } : {}), + }, + ); + + if (targetDecl.length === 1) { + targetDecl.at(0).get().node.specifiers.push( + j.importSpecifier(j.identifier(name)), + ); + } else { + // Create the new import specifier + root.find(j.Program).at(0).nodes()[0].body.unshift( + j.importDeclaration( + [j.importSpecifier(j.identifier(name))], + j.stringLiteral(to), + isTypeOnly ? "type" : "value", + ), + ); + } + + return root.toSource(); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/refactorGetXCached.ts b/packages/maintenance/src/codeshifts/refactorGetXCached.ts new file mode 100644 index 000000000000..558d063d94ab --- /dev/null +++ b/packages/maintenance/src/codeshifts/refactorGetXCached.ts @@ -0,0 +1,113 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type Identifier, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + {}: Options, +) => { + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const ccImplementations = root.find(j.ClassDeclaration, { + superClass: { + name: "CommandClass", + }, + }); + + const methods = ccImplementations.find(j.ClassMethod, { + kind: "method", + static: true, + }).filter(({ node }) => { + return node.key.type === "Identifier" + && node.key.name.endsWith("Cached") + && node.params.length >= 1 + && node.params[0].type === "Identifier" + && node.params[0].name === "applHost" + && node.params[0].typeAnnotation?.type === "TSTypeAnnotation" + && node.params[0].typeAnnotation.typeAnnotation.type + === "TSTypeReference" + && node.params[0].typeAnnotation.typeAnnotation.typeName.type + === "Identifier" + && node.params[0].typeAnnotation.typeAnnotation.typeName.name + === "ZWaveApplicationHost"; + }); + + methods.replaceWith(({ node }) => { + const ident = node.params[0] as Identifier; + ident.name = "ctx"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeName.name = "GetValueDB"; + return node; + }); + + const memberExprs = methods.find(j.MemberExpression, { + object: { + type: "Identifier", + name: "applHost", + }, + }); + + memberExprs.replaceWith(({ node }) => { + node.object = j.identifier("ctx"); + return node; + }); + + return root.toSource(); + + // const imp = root + // .find( + // j.ImportDeclaration, + // { importKind: typeOnly ? "type" : undefined }, + // ) + // .find(j.ImportSpecifier, { + // imported: { + // name: from, + // }, + // }); + + // return imp.replaceWith(({ node }) => { + // node.imported.name = to; + // return node; + // }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/refactorInterview.ts b/packages/maintenance/src/codeshifts/refactorInterview.ts new file mode 100644 index 000000000000..5d1e6c077945 --- /dev/null +++ b/packages/maintenance/src/codeshifts/refactorInterview.ts @@ -0,0 +1,124 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type Identifier, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + {}: Options, +) => { + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const ccImplementations = root.find(j.ClassDeclaration, { + superClass: { + name: "CommandClass", + }, + }); + + const methods = ccImplementations.find(j.ClassMethod, { + kind: "method", + }).filter(({ node }) => { + return node.key.type === "Identifier" + && (node.key.name === "interview") + && node.params.length >= 1 + && node.params[0].type === "Identifier" + && node.params[0].name === "applHost" + && node.params[0].typeAnnotation?.type === "TSTypeAnnotation" + && node.params[0].typeAnnotation.typeAnnotation.type + === "TSTypeReference" + && node.params[0].typeAnnotation.typeAnnotation.typeName.type + === "Identifier" + && node.params[0].typeAnnotation.typeAnnotation.typeName.name + === "ZWaveApplicationHost"; + }); + + if (!methods.length) return file.source; + + methods.replaceWith(({ node }) => { + const ident = node.params[0] as Identifier; + ident.name = "ctx"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeName.name = "InterviewContext"; + return node; + }); + + const paramUsages = methods.find(j.Identifier, { + name: "applHost", + }); + paramUsages.replaceWith(({ node }) => { + node.name = "ctx"; + return node; + }); + + const targetDecl = root + .find( + j.ImportDeclaration, + { + source: { + type: "StringLiteral", + value: "../lib/CommandClass", + }, + }, + ); + targetDecl.at(0).get().node.specifiers.push( + j.importSpecifier(j.identifier("InterviewContext")), + ); + + return root.toSource(); + + // const imp = root + // .find( + // j.ImportDeclaration, + // { importKind: typeOnly ? "type" : undefined }, + // ) + // .find(j.ImportSpecifier, { + // imported: { + // name: from, + // }, + // }); + + // return imp.replaceWith(({ node }) => { + // node.imported.name = to; + // return node; + // }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/refactorPersistValues.ts b/packages/maintenance/src/codeshifts/refactorPersistValues.ts new file mode 100644 index 000000000000..d0a7f40c7856 --- /dev/null +++ b/packages/maintenance/src/codeshifts/refactorPersistValues.ts @@ -0,0 +1,129 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type Identifier, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + {}: Options, +) => { + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const ccImplementations = root.find(j.ClassDeclaration, { + superClass(value) { + return value?.type === "Identifier" && value.name.endsWith("CC"); + }, + }); + + const methods = ccImplementations.find(j.ClassMethod, { + kind: "method", + }).filter(({ node }) => { + return node.key.type === "Identifier" + && (node.key.name === "persistValues") + && node.params.length >= 1 + && node.params[0].type === "Identifier" + && node.params[0].name.endsWith("applHost") + && node.params[0].typeAnnotation?.type === "TSTypeAnnotation" + && node.params[0].typeAnnotation.typeAnnotation.type + === "TSTypeReference" + && node.params[0].typeAnnotation.typeAnnotation.typeName.type + === "Identifier" + && node.params[0].typeAnnotation.typeAnnotation.typeName.name + === "ZWaveApplicationHost"; + }); + + if (!methods.length) return file.source; + + methods.replaceWith(({ node }) => { + const ident = node.params[0] as Identifier; + ident.name = "ctx"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeName.name = + "PersistValuesContext"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeParameters = undefined; + return node; + }); + + const paramUsages = methods.find(j.Identifier, { + name(value) { + return value.endsWith("applHost"); + }, + }); + paramUsages.replaceWith(({ node }) => { + node.name = "ctx"; + return node; + }); + + const targetDecl = root + .find( + j.ImportDeclaration, + { + source: { + type: "StringLiteral", + value: "../lib/CommandClass", + }, + }, + ); + targetDecl.at(0).get().node.specifiers.push( + j.importSpecifier(j.identifier("PersistValuesContext")), + ); + + return root.toSource(); + + // const imp = root + // .find( + // j.ImportDeclaration, + // { importKind: typeOnly ? "type" : undefined }, + // ) + // .find(j.ImportSpecifier, { + // imported: { + // name: from, + // }, + // }); + + // return imp.replaceWith(({ node }) => { + // node.imported.name = to; + // return node; + // }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/refactorRefreshValues.ts b/packages/maintenance/src/codeshifts/refactorRefreshValues.ts new file mode 100644 index 000000000000..a6d9c58e9c93 --- /dev/null +++ b/packages/maintenance/src/codeshifts/refactorRefreshValues.ts @@ -0,0 +1,125 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type Identifier, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + {}: Options, +) => { + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const ccImplementations = root.find(j.ClassDeclaration, { + superClass: { + name: "CommandClass", + }, + }); + + const methods = ccImplementations.find(j.ClassMethod, { + kind: "method", + }).filter(({ node }) => { + return node.key.type === "Identifier" + && (node.key.name === "refreshValues") + && node.params.length >= 1 + && node.params[0].type === "Identifier" + && node.params[0].name === "applHost" + && node.params[0].typeAnnotation?.type === "TSTypeAnnotation" + && node.params[0].typeAnnotation.typeAnnotation.type + === "TSTypeReference" + && node.params[0].typeAnnotation.typeAnnotation.typeName.type + === "Identifier" + && node.params[0].typeAnnotation.typeAnnotation.typeName.name + === "ZWaveApplicationHost"; + }); + + if (!methods.length) return file.source; + + methods.replaceWith(({ node }) => { + const ident = node.params[0] as Identifier; + ident.name = "ctx"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeName.name = + "RefreshValuesContext"; + return node; + }); + + const paramUsages = methods.find(j.Identifier, { + name: "applHost", + }); + paramUsages.replaceWith(({ node }) => { + node.name = "ctx"; + return node; + }); + + const targetDecl = root + .find( + j.ImportDeclaration, + { + source: { + type: "StringLiteral", + value: "../lib/CommandClass", + }, + }, + ); + targetDecl.at(0).get().node.specifiers.push( + j.importSpecifier(j.identifier("RefreshValuesContext")), + ); + + return root.toSource(); + + // const imp = root + // .find( + // j.ImportDeclaration, + // { importKind: typeOnly ? "type" : undefined }, + // ) + // .find(j.ImportSpecifier, { + // imported: { + // name: from, + // }, + // }); + + // return imp.replaceWith(({ node }) => { + // node.imported.name = to; + // return node; + // }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/refactorTranslateProperty.ts b/packages/maintenance/src/codeshifts/refactorTranslateProperty.ts new file mode 100644 index 000000000000..3d5ba16f5015 --- /dev/null +++ b/packages/maintenance/src/codeshifts/refactorTranslateProperty.ts @@ -0,0 +1,111 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type Identifier, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + {}: Options, +) => { + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const ccImplementations = root.find(j.ClassDeclaration, { + superClass: { + name: "CommandClass", + }, + }); + + const methods = ccImplementations.find(j.ClassMethod, { + kind: "method", + }).filter(({ node }) => { + return node.key.type === "Identifier" + && (node.key.name === "translateProperty" + || node.key.name === "translatePropertyKey") + && node.params.length >= 1 + && node.params[0].type === "Identifier" + && node.params[0].name === "applHost" + && node.params[0].typeAnnotation?.type === "TSTypeAnnotation" + && node.params[0].typeAnnotation.typeAnnotation.type + === "TSTypeReference" + && node.params[0].typeAnnotation.typeAnnotation.typeName.type + === "Identifier" + && node.params[0].typeAnnotation.typeAnnotation.typeName.name + === "ZWaveApplicationHost"; + }); + + if (!methods.length) return file.source; + + methods.replaceWith(({ node }) => { + const ident = node.params[0] as Identifier; + ident.name = "ctx"; + // @ts-expect-error + ident.typeAnnotation.typeAnnotation.typeName.name = "GetValueDB"; + return node; + }); + + const paramUsages = methods.find(j.Identifier, { + name: "applHost", + }); + paramUsages.replaceWith(({ node }) => { + node.name = "ctx"; + return node; + }); + + return root.toSource(); + + // const imp = root + // .find( + // j.ImportDeclaration, + // { importKind: typeOnly ? "type" : undefined }, + // ) + // .find(j.ImportSpecifier, { + // imported: { + // name: from, + // }, + // }); + + // return imp.replaceWith(({ node }) => { + // node.imported.name = to; + // return node; + // }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/maintenance/src/codeshifts/renameImport.ts b/packages/maintenance/src/codeshifts/renameImport.ts new file mode 100644 index 000000000000..d1214372dab9 --- /dev/null +++ b/packages/maintenance/src/codeshifts/renameImport.ts @@ -0,0 +1,74 @@ +// Codemod to rename imports across the entire codebase. +// Run with jscodeshift: https://jscodeshift.com/run/cli/ +// options: +// from: the name of the import to rename +// to: the new name of the import +// typeOnly: whether to only rename type imports (optional, default false) + +// examples: https://github.com/wingy3181/jscodeshift-examples/tree/master/src/examples + +import { + type API, + type FileInfo, + type JSCodeshift, + type Options, + type Transform, +} from "jscodeshift"; + +const transform: Transform = ( + file: FileInfo, + api: API, + { + from, + to, + typeOnly = false, + }: Options, +) => { + if (!from || !to) { + throw new Error("Both 'from' and 'to' are required options"); + } + + const j: JSCodeshift = api.jscodeshift; + const root = j(file.source); + + const imp = root + .find( + j.ImportDeclaration, + { importKind: typeOnly ? "type" : undefined }, + ) + .find(j.ImportSpecifier, { + imported: { + name: from, + }, + }); + + return imp.replaceWith(({ node }) => { + node.imported.name = to; + return node; + }).toSource(); + + // return ( + // j(file.source) + // .find(j.FunctionDeclaration) + // // Target a particular function declaration + // .filter(({ node: functionDeclaration }) => functionDeclaration.id.name === functionName) + // .replaceWith(({ node: functionDeclaration }) => { + // // Create a function call expression statement + // const functionCallExpressionStatement = j.expressionStatement( + // j.callExpression(j.identifier(functionNameToCall), []) + // ); + + // // Create a comment and add it as a leading comment to the function call expression + // const commentLine = j.commentLine(comment); + // functionCallExpressionStatement.comments = [commentLine]; + + // // Append the function call expression to the function declaration's existing body + // functionDeclaration.body.body = [...functionDeclaration.body.body, functionCallExpressionStatement]; + // return functionDeclaration; + // }) + // .toSource() + // ); +}; + +export default transform; +export const parser = "ts"; diff --git a/packages/serial/src/message/Message.test.ts b/packages/serial/src/message/Message.test.ts index 72be3ca3b0af..5eff3b02e893 100644 --- a/packages/serial/src/message/Message.test.ts +++ b/packages/serial/src/message/Message.test.ts @@ -6,7 +6,6 @@ import type { INodeQuery } from "./INodeQuery"; import { Message, messageTypes } from "./Message"; test("should deserialize and serialize correctly", (t) => { - const host = createTestingHost(); // actual messages from OZW const okayMessages = [ Buffer.from([ @@ -40,24 +39,22 @@ test("should deserialize and serialize correctly", (t) => { ]), ]; for (const original of okayMessages) { - const parsed = new Message(host, { data: original }); - t.deepEqual(parsed.serialize(), original); + const parsed = new Message({ data: original, ctx: {} as any }); + t.deepEqual(parsed.serialize({} as any), original); } }); test("should serialize correctly when the payload is null", (t) => { - const host = createTestingHost(); // synthetic message const expected = Buffer.from([0x01, 0x03, 0x00, 0xff, 0x03]); - const message = new Message(host, { + const message = new Message({ type: MessageType.Request, - functionType: 0xff, + functionType: 0xff as any, }); - t.deepEqual(message.serialize(), expected); + t.deepEqual(message.serialize({} as any), expected); }); test("should throw the correct error when parsing a faulty message", (t) => { - const host = createTestingHost(); // fake messages to produce certain errors const brokenMessages: [Buffer, string, ZWaveErrorCodes][] = [ // too short (<5 bytes) @@ -92,10 +89,14 @@ test("should throw the correct error when parsing a faulty message", (t) => { ], ]; for (const [message, msg, code] of brokenMessages) { - assertZWaveError(t, () => new Message(host, { data: message }), { - messageMatches: msg, - errorCode: code, - }); + assertZWaveError( + t, + () => new Message({ data: message, ctx: {} as any }), + { + messageMatches: msg, + errorCode: code, + }, + ); } }); @@ -248,8 +249,7 @@ test("isComplete() should work correctly", (t) => { }); test("toJSON() should return a semi-readable JSON representation", (t) => { - const host = createTestingHost(); - const msg1 = new Message(host, { + const msg1 = new Message({ type: MessageType.Request, functionType: FunctionType.GetControllerVersion, }); @@ -259,7 +259,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { functionType: "GetControllerVersion", payload: "", }; - const msg2 = new Message(host, { + const msg2 = new Message({ type: MessageType.Request, functionType: FunctionType.GetControllerVersion, payload: Buffer.from("aabbcc", "hex"), @@ -270,7 +270,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { functionType: "GetControllerVersion", payload: "aabbcc", }; - const msg3 = new Message(host, { + const msg3 = new Message({ type: MessageType.Response, functionType: FunctionType.GetControllerVersion, expectedResponse: FunctionType.GetControllerVersion, @@ -282,7 +282,7 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { expectedResponse: "GetControllerVersion", payload: "", }; - const msg4 = new Message(host, { + const msg4 = new Message({ type: MessageType.Request, functionType: FunctionType.GetControllerVersion, expectedResponse: FunctionType.GetControllerVersion, @@ -308,26 +308,28 @@ test("getConstructor() should return `Message` for an unknown packet type", (t) }); test(`the constructor should throw when no message type is specified`, (t) => { - const host = createTestingHost(); - assertZWaveError(t, () => new Message(host, { functionType: 0xff }), { - errorCode: ZWaveErrorCodes.Argument_Invalid, - messageMatches: /message type/i, - }); + assertZWaveError( + t, + () => new Message({ functionType: 0xff as any }), + { + errorCode: ZWaveErrorCodes.Argument_Invalid, + messageMatches: /message type/i, + }, + ); - @messageTypes(undefined as any, 0xff) + @messageTypes(undefined as any, 0xff as any) class FakeMessageWithoutMessageType extends Message {} - assertZWaveError(t, () => new FakeMessageWithoutMessageType(host), { + assertZWaveError(t, () => new FakeMessageWithoutMessageType(), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /message type/i, }); }); test(`the constructor should throw when no function type is specified`, (t) => { - const host = createTestingHost(); assertZWaveError( t, - () => new Message(host, { type: MessageType.Request }), + () => new Message({ type: MessageType.Request }), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /function type/i, @@ -337,44 +339,44 @@ test(`the constructor should throw when no function type is specified`, (t) => { @messageTypes(MessageType.Request, undefined as any) class FakeMessageWithoutFunctionType extends Message {} - assertZWaveError(t, () => new FakeMessageWithoutFunctionType(host), { + assertZWaveError(t, () => new FakeMessageWithoutFunctionType(), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /function type/i, }); }); -test("getNodeUnsafe() returns undefined when the controller is not initialized yet", (t) => { +test("tryGetNode() returns undefined when the controller is not initialized yet", (t) => { const host = createTestingHost(); - const msg = new Message(host, { + const msg = new Message({ type: MessageType.Request, - functionType: 0xff, + functionType: 0xff as any, }); - t.is(msg.getNodeUnsafe(host), undefined); + t.is(msg.tryGetNode(host), undefined); }); -test("getNodeUnsafe() returns undefined when the message is no node query", (t) => { +test("tryGetNode() returns undefined when the message is no node query", (t) => { const host = createTestingHost(); - const msg = new Message(host, { + const msg = new Message({ type: MessageType.Request, - functionType: 0xff, + functionType: 0xff as any, }); - t.is(msg.getNodeUnsafe(host), undefined); + t.is(msg.tryGetNode(host), undefined); }); -test("getNodeUnsafe() returns the associated node otherwise", (t) => { +test("tryGetNode() returns the associated node otherwise", (t) => { const host = createTestingHost(); - host.nodes.set(1, {} as any); + host.setNode(1, {} as any); - const msg = new Message(host, { + const msg = new Message({ type: MessageType.Request, - functionType: 0xff, + functionType: 0xff as any, }); // This node exists (msg as any as INodeQuery).nodeId = 1; - t.is(msg.getNodeUnsafe(host), host.nodes.get(1)); + t.is(msg.tryGetNode(host), host.getNode(1)); // This one does (msg as any as INodeQuery).nodeId = 2; - t.is(msg.getNodeUnsafe(host), undefined); + t.is(msg.tryGetNode(host), undefined); }); diff --git a/packages/serial/src/message/Message.ts b/packages/serial/src/message/Message.ts index 1937cfbb78ce..4bf25cbafcd7 100644 --- a/packages/serial/src/message/Message.ts +++ b/packages/serial/src/message/Message.ts @@ -1,14 +1,23 @@ import { - type IZWaveNode, + type MaybeNotKnown, type MessageOrCCLogEntry, type MessagePriority, + type NodeIDType, + type NodeId, + type SecurityClass, + type SecurityManagers, ZWaveError, ZWaveErrorCodes, createReflectionDecorator, getNodeTag, highResTimestamp, } from "@zwave-js/core"; -import type { ZWaveApplicationHost, ZWaveHost } from "@zwave-js/host"; +import type { + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + HostIDs, +} from "@zwave-js/host"; import type { JSONObject, TypedClassDecorator } from "@zwave-js/shared/safe"; import { num2hex, staticExtends } from "@zwave-js/shared/safe"; import { MessageHeaders } from "../MessageHeaders"; @@ -16,12 +25,10 @@ import { FunctionType, MessageType } from "./Constants"; import { isNodeQuery } from "./INodeQuery"; export type MessageConstructor = new ( - host: ZWaveHost, options?: MessageOptions, ) => T; export type DeserializingMessageConstructor = new ( - host: ZWaveHost, options: MessageDeserializationOptions, ) => T; @@ -31,6 +38,26 @@ export enum MessageOrigin { Host, } +export interface MessageParsingContext + extends Readonly, HostIDs, GetDeviceConfig +{ + /** How many bytes a node ID occupies in serial API commands */ + nodeIdType: NodeIDType; + + getHighestSecurityClass(nodeId: number): MaybeNotKnown; + + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown; + + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void; +} + export interface MessageDeserializationOptions { data: Buffer; origin?: MessageOrigin; @@ -40,6 +67,8 @@ export interface MessageDeserializationOptions { sdkVersion?: string; /** Optional context used during deserialization */ context?: unknown; + // FIXME: This is a terrible property name when context already exists + ctx: MessageParsingContext; } /** @@ -67,13 +96,36 @@ export type MessageOptions = | MessageCreationOptions | MessageDeserializationOptions; +export interface MessageEncodingContext + extends + Readonly, + HostIDs, + GetSupportedCCVersion, + GetDeviceConfig +{ + /** How many bytes a node ID occupies in serial API commands */ + nodeIdType: NodeIDType; + + getHighestSecurityClass(nodeId: number): MaybeNotKnown; + + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown; + + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void; +} + /** * Represents a Z-Wave message for communication with the serial interface */ export class Message { public constructor( - public readonly host: ZWaveHost, - options: MessageOptions = {}, + public readonly options: MessageOptions = {}, ) { // decide which implementation we follow if (gotDeserializationOptions(options)) { @@ -145,7 +197,7 @@ export class Message { this.expectedCallback = options.expectedCallback ?? getExpectedCallback(this); - this._callbackId = options.callbackId; + this.callbackId = options.callbackId; this.payload = options.payload || Buffer.allocUnsafe(0); } @@ -165,28 +217,23 @@ export class Message { | undefined; public payload: Buffer; // TODO: Length limit 255 - private _callbackId: number | undefined; - /** - * Used to map requests to responses. - * - * WARNING: Accessing this property will generate a new callback ID if this message had none. - * If you want to compare the callback ID, use `hasCallbackId()` beforehand to check if the callback ID is already defined. - */ - public get callbackId(): number { - if (this._callbackId == undefined) { - this._callbackId = this.host.getNextCallbackId(); + /** Used to map requests to callbacks */ + public callbackId: number | undefined; + + protected assertCallbackId(): asserts this is this & { + callbackId: number; + } { + if (this.callbackId == undefined) { + throw new ZWaveError( + "Callback ID required but not set", + ZWaveErrorCodes.PacketFormat_Invalid, + ); } - return this._callbackId; - } - public set callbackId(v: number | undefined) { - this._callbackId = v; } - /** - * Tests whether this message's callback ID is defined - */ - public hasCallbackId(): boolean { - return this._callbackId != undefined; + /** Returns whether the callback ID is set */ + public hasCallbackId(): this is this & { callbackId: number } { + return this.callbackId != undefined; } /** @@ -209,7 +256,8 @@ export class Message { } /** Serializes this message into a Buffer */ - public serialize(): Buffer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public serialize(ctx: MessageEncodingContext): Buffer { const ret = Buffer.allocUnsafe(this.payload.length + 5); ret[0] = MessageHeaders.SOF; // length of the following data, including the checksum @@ -251,7 +299,6 @@ export class Message { /** Creates an instance of the message that is serialized in the given buffer */ public static from( - host: ZWaveHost, options: MessageDeserializationOptions, contextStore?: Map>, ): Message { @@ -266,7 +313,7 @@ export class Message { } } - const ret = new Constructor(host, options); + const ret = new Constructor(options); return ret; } @@ -379,11 +426,7 @@ export class Message { // To prevent this from triggering the unresponsive controller detection we need to forward these messages as if they were correct if (msg.functionType !== 0 as any) { // If a received request included a callback id, enforce that the response contains the same - if ( - this.hasCallbackId() - && (!msg.hasCallbackId() - || this._callbackId !== msg._callbackId) - ) { + if (this.callbackId !== msg.callbackId) { return false; } } @@ -410,11 +453,11 @@ export class Message { /** * Returns the node this message is linked to or undefined */ - public getNodeUnsafe( - applHost: ZWaveApplicationHost, - ): IZWaveNode | undefined { + public tryGetNode( + ctx: GetNode, + ): T | undefined { const nodeId = this.getNodeId(); - if (nodeId != undefined) return applHost.nodes.get(nodeId); + if (nodeId != undefined) return ctx.getNode(nodeId); } private _transmissionTimestamp: number | undefined; diff --git a/packages/serial/src/message/ZnifferMessages.ts b/packages/serial/src/message/ZnifferMessages.ts index dddd22e70d9d..de4cc9ed5b49 100644 --- a/packages/serial/src/message/ZnifferMessages.ts +++ b/packages/serial/src/message/ZnifferMessages.ts @@ -59,7 +59,6 @@ export type ZnifferMessageOptions = */ export class ZnifferMessage { public constructor( - // public readonly host: ZWaveHost, options: ZnifferMessageOptions, ) { // decide which implementation we follow diff --git a/packages/testing/src/MockController.ts b/packages/testing/src/MockController.ts index 2b26e3d4193b..7aef7607df75 100644 --- a/packages/testing/src/MockController.ts +++ b/packages/testing/src/MockController.ts @@ -1,14 +1,23 @@ -import { type ICommandClass, MAX_SUPERVISION_SESSION_ID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; +import { + type CCId, + type MaybeNotKnown, + NOT_KNOWN, + NodeIDType, + SecurityClass, + type SecurityManagers, + securityClassOrder, +} from "@zwave-js/core"; import { Message, + type MessageEncodingContext, MessageHeaders, MessageOrigin, + type MessageParsingContext, SerialAPIParser, } from "@zwave-js/serial"; import type { MockPortBinding } from "@zwave-js/serial/mock"; import { AsyncQueue } from "@zwave-js/shared"; -import { TimedExpectation, createWrappingCounter } from "@zwave-js/shared/safe"; +import { TimedExpectation } from "@zwave-js/shared/safe"; import { wait } from "alcalzone-shared/async"; import { randomInt } from "node:crypto"; import { @@ -49,26 +58,57 @@ export class MockController { // const valuesStorage = new Map(); // const metadataStorage = new Map(); // const valueDBCache = new Map(); - const supervisionSessionIDs = new Map number>(); - - this.host = { - ownNodeId: options.ownNodeId ?? 1, - homeId: options.homeId ?? 0x7e571000, - securityManager: undefined, - securityManager2: undefined, - securityManagerLR: undefined, - // nodes: this.nodes as any, - getNextCallbackId: () => 1, - getNextSupervisionSessionId: (nodeId) => { - if (!supervisionSessionIDs.has(nodeId)) { - supervisionSessionIDs.set( - nodeId, - createWrappingCounter(MAX_SUPERVISION_SESSION_ID, true), - ); + // const supervisionSessionIDs = new Map number>(); + + this.ownNodeId = options.ownNodeId ?? 1; + this.homeId = options.homeId ?? 0x7e571000; + + this.capabilities = { + ...getDefaultMockControllerCapabilities(), + ...options.capabilities, + }; + + const securityClasses = new Map>(); + + const self = this; + this.encodingContext = { + homeId: this.homeId, + ownNodeId: this.ownNodeId, + // TODO: LR is not supported in mocks + nodeIdType: NodeIDType.Short, + hasSecurityClass( + nodeId: number, + securityClass: SecurityClass, + ): MaybeNotKnown { + return ( + securityClasses.get(nodeId)?.get(securityClass) ?? NOT_KNOWN + ); + }, + setSecurityClass( + nodeId: number, + securityClass: SecurityClass, + granted: boolean, + ): void { + if (!securityClasses.has(nodeId)) { + securityClasses.set(nodeId, new Map()); + } + securityClasses.get(nodeId)!.set(securityClass, granted); + }, + getHighestSecurityClass( + nodeId: number, + ): MaybeNotKnown { + const map = securityClasses.get(nodeId); + if (!map?.size) return undefined; + let missingSome = false; + for (const secClass of securityClassOrder) { + if (map.get(secClass) === true) return secClass; + if (!map.has(secClass)) { + missingSome = true; + } } - return supervisionSessionIDs.get(nodeId)!(); + // If we don't have the info for every security class, we don't know the highest one yet + return missingSome ? undefined : SecurityClass.None; }, - getSafeCCVersion: () => 100, getSupportedCCVersion: (cc, nodeId, endpointIndex = 0) => { if (!this.nodes.has(nodeId)) { return 0; @@ -77,35 +117,36 @@ export class MockController { const endpoint = node.endpoints.get(endpointIndex); return (endpoint ?? node).implementedCCs.get(cc)?.version ?? 0; }, - isCCSecure: () => false, - // TODO: We don't care about security classes on the controller - // This is handled by the nodes hosts - getHighestSecurityClass: () => undefined, - hasSecurityClass: () => false, - setSecurityClass: () => {}, - // getValueDB: (nodeId) => { - // if (!valueDBCache.has(nodeId)) { - // valueDBCache.set( - // nodeId, - // new ValueDB( - // nodeId, - // valuesStorage as any, - // metadataStorage as any, - // ), - // ); - // } - // return valueDBCache.get(nodeId)!; - // }, + getDeviceConfig: () => undefined, + get securityManager() { + return self.securityManagers.securityManager; + }, + get securityManager2() { + return self.securityManagers.securityManager2; + }, + get securityManagerLR() { + return self.securityManagers.securityManagerLR; + }, }; - - this.capabilities = { - ...getDefaultMockControllerCapabilities(), - ...options.capabilities, + this.parsingContext = { + ...this.encodingContext, }; void this.execute(); } + public homeId: number; + public ownNodeId: number; + + public securityManagers: SecurityManagers = { + securityManager: undefined, + securityManager2: undefined, + securityManagerLR: undefined, + }; + + public encodingContext: MessageEncodingContext; + public parsingContext: MessageParsingContext; + public readonly serial: MockPortBinding; private readonly serialParser: SerialAPIParser; @@ -145,8 +186,6 @@ export class MockController { this._nodes.delete(node.id); } - public readonly host: ZWaveHost; - public readonly capabilities: MockControllerCapabilities; /** Can be used by behaviors to store controller related state */ @@ -189,10 +228,11 @@ export class MockController { let msg: Message; try { - msg = Message.from(this.host, { + msg = Message.from({ data, origin: MessageOrigin.Host, parseCCs: false, + ctx: this.parsingContext, }); this._receivedHostMessages.push(msg); if (this.autoAckHostMessages) { @@ -213,7 +253,7 @@ export class MockController { handler.resolve(msg); } else { for (const behavior of this.behaviors) { - if (await behavior.onHostMessage?.(this.host, this, msg)) { + if (await behavior.onHostMessage?.(this, msg)) { return; } } @@ -301,10 +341,10 @@ export class MockController { * * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected */ - public async expectNodeCC( + public async expectNodeCC( node: MockNode, timeout: number, - predicate: (cc: ICommandClass) => cc is T, + predicate: (cc: CCId) => cc is T, ): Promise { const ret = await this.expectNodeFrame( node, @@ -338,6 +378,27 @@ export class MockController { this.serial.emitData(Buffer.from([data])); } + /** Sends a raw buffer to the host/driver and expect an ACK */ + public async sendMessageToHost( + msg: Message, + fromNode?: MockNode, + ): Promise { + let data: Buffer; + if (fromNode) { + data = msg.serialize({ + nodeIdType: this.encodingContext.nodeIdType, + ...fromNode.encodingContext, + }); + // Simulate the frame being transmitted via radio + await wait(fromNode.capabilities.txDelay); + } else { + data = msg.serialize(this.encodingContext); + } + this.serial.emitData(data); + // TODO: make the timeout match the configured ACK timeout + await this.expectHostACK(1000); + } + /** Sends a raw buffer to the host/driver and expect an ACK */ public async sendToHost(data: Buffer): Promise { this.serial.emitData(data); @@ -382,7 +443,7 @@ export class MockController { // Then apply generic predefined behavior for (const behavior of this.behaviors) { if ( - await behavior.onNodeFrame?.(this.host, this, node, frame) + await behavior.onNodeFrame?.(this, node, frame) ) { return; } @@ -488,13 +549,11 @@ export class MockController { export interface MockControllerBehavior { /** Gets called when a message from the host is received. Return `true` to indicate that the message has been handled. */ onHostMessage?: ( - host: ZWaveHost, controller: MockController, msg: Message, ) => Promise | boolean | undefined; /** Gets called when a message from a node is received. Return `true` to indicate that the message has been handled. */ onNodeFrame?: ( - host: ZWaveHost, controller: MockController, node: MockNode, frame: MockZWaveFrame, diff --git a/packages/testing/src/MockNode.ts b/packages/testing/src/MockNode.ts index 7d869e7c5cb4..5b26aecf214a 100644 --- a/packages/testing/src/MockNode.ts +++ b/packages/testing/src/MockNode.ts @@ -5,9 +5,10 @@ import { type MaybeNotKnown, NOT_KNOWN, SecurityClass, + type SecurityManagers, securityClassOrder, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; +import type { CCEncodingContext } from "@zwave-js/host"; import { TimedExpectation } from "@zwave-js/shared"; import { isDeepStrictEqual } from "node:util"; import type { CCIdToCapabilities } from "./CCSpecificCapabilities"; @@ -111,14 +112,44 @@ export class MockNode { this.id = options.id; this.controller = options.controller; - // A node's host is a bit more specialized than the controller's host. const securityClasses = new Map>(); - this.host = { - ...this.controller.host, - ownNodeId: this.id, - __internalIsMockNode: true, - // Mimic the behavior of ZWaveNode, but for arbitrary node IDs + const { + commandClasses = [], + endpoints = [], + ...capabilities + } = options.capabilities ?? {}; + this.capabilities = { + ...getDefaultMockNodeCapabilities(), + ...capabilities, + }; + + for (const cc of commandClasses) { + if (typeof cc === "number") { + this.addCC(cc, {}); + } else { + const { ccId, ...ccInfo } = cc; + this.addCC(ccId, ccInfo); + } + } + + let index = 0; + for (const endpoint of endpoints) { + index++; + this.endpoints.set( + index, + new MockEndpoint({ + index, + node: this, + capabilities: endpoint, + }), + ); + } + + const self = this; + this.encodingContext = { + homeId: this.controller.homeId, + ownNodeId: this.id, hasSecurityClass( nodeId: number, securityClass: SecurityClass, @@ -152,46 +183,37 @@ export class MockNode { // If we don't have the info for every security class, we don't know the highest one yet return missingSome ? undefined : SecurityClass.None; }, + getSupportedCCVersion: (cc, nodeId, endpointIndex = 0) => { + // Mock endpoints only care about the version they implement + const endpoint = this.endpoints.get(endpointIndex); + return (endpoint ?? this).implementedCCs.get(cc)?.version ?? 0; + }, + // Mock nodes don't care about device configuration files + getDeviceConfig: () => undefined, + get securityManager() { + return self.securityManagers.securityManager; + }, + get securityManager2() { + return self.securityManagers.securityManager2; + }, + get securityManagerLR() { + return self.securityManagers.securityManagerLR; + }, }; - - const { - commandClasses = [], - endpoints = [], - ...capabilities - } = options.capabilities ?? {}; - this.capabilities = { - ...getDefaultMockNodeCapabilities(), - ...capabilities, - }; - - for (const cc of commandClasses) { - if (typeof cc === "number") { - this.addCC(cc, {}); - } else { - const { ccId, ...ccInfo } = cc; - this.addCC(ccId, ccInfo); - } - } - - let index = 0; - for (const endpoint of endpoints) { - index++; - this.endpoints.set( - index, - new MockEndpoint({ - index, - node: this, - capabilities: endpoint, - }), - ); - } } - public readonly host: ZWaveHost; public readonly id: number; public readonly controller: MockController; public readonly capabilities: MockNodeCapabilities; + public securityManagers: SecurityManagers = { + securityManager: undefined, + securityManager2: undefined, + securityManagerLR: undefined, + }; + + public encodingContext: CCEncodingContext; + private behaviors: MockNodeBehavior[] = []; public readonly implementedCCs = new Map< diff --git a/packages/zwave-js/src/lib/controller/Controller.manageAssociations.test.ts b/packages/zwave-js/src/lib/controller/Controller.manageAssociations.test.ts index f4c18c07ed7b..27bb3d5d9d0a 100644 --- a/packages/zwave-js/src/lib/controller/Controller.manageAssociations.test.ts +++ b/packages/zwave-js/src/lib/controller/Controller.manageAssociations.test.ts @@ -7,43 +7,35 @@ import { import { CommandClasses, SecurityClass } from "@zwave-js/core/safe"; import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -import { createTestNode } from "../test/mocks"; +import { type CreateTestNodeOptions, createTestNode } from "../test/mocks"; test("associations between insecure nodes are allowed", (t) => { // This test simulates two Zooz ZEN76 switches, included insecurely - const host = createTestingHost({ - getSupportedCCVersion(cc, nodeId, endpointIndex) { - switch (cc) { - case CommandClasses["Association Group Information"]: - return 1; - case CommandClasses.Association: - return 3; - case CommandClasses["Multi Channel Association"]: - return 4; - case CommandClasses.Basic: - return 2; - case CommandClasses["Binary Switch"]: - return 2; - } - return 0; + const host = createTestingHost(); + + const commandClasses: CreateTestNodeOptions["commandClasses"] = { + [CommandClasses["Association Group Information"]]: { + version: 1, }, - }); + [CommandClasses.Association]: { + version: 3, + }, + [CommandClasses["Multi Channel Association"]]: { + version: 4, + }, + [CommandClasses.Basic]: { + version: 2, + }, + [CommandClasses["Binary Switch"]]: { + version: 2, + }, + }; const node2 = createTestNode(host, { id: 2, - supportsCC(cc) { - switch (cc) { - case CommandClasses["Association Group Information"]: - case CommandClasses.Association: - case CommandClasses["Multi Channel Association"]: - case CommandClasses.Basic: - case CommandClasses["Binary Switch"]: - return true; - } - return false; - }, + commandClasses, }); - host.nodes.set(node2.id, node2); + host.setNode(node2.id, node2); node2.setSecurityClass(SecurityClass.S0_Legacy, false); node2.setSecurityClass(SecurityClass.S2_AccessControl, false); @@ -52,19 +44,9 @@ test("associations between insecure nodes are allowed", (t) => { const node3 = createTestNode(host, { id: 3, - supportsCC(cc) { - switch (cc) { - case CommandClasses["Association Group Information"]: - case CommandClasses.Association: - case CommandClasses["Multi Channel Association"]: - case CommandClasses.Basic: - case CommandClasses["Binary Switch"]: - return true; - } - return false; - }, + commandClasses, }); - host.nodes.set(node3.id, node3); + host.setNode(node3.id, node3); node3.setSecurityClass(SecurityClass.S0_Legacy, false); node3.setSecurityClass(SecurityClass.S2_AccessControl, false); diff --git a/packages/zwave-js/src/lib/controller/Controller.ts b/packages/zwave-js/src/lib/controller/Controller.ts index 2618142275fb..0730dc4ea047 100644 --- a/packages/zwave-js/src/lib/controller/Controller.ts +++ b/packages/zwave-js/src/lib/controller/Controller.ts @@ -42,13 +42,13 @@ import { import { type IndicatorObject } from "@zwave-js/cc/IndicatorCC"; import { BasicDeviceClass, + type CCId, CommandClasses, type ControllerCapabilities, ControllerRole, ControllerStatus, EMPTY_ROUTE, type Firmware, - type ICommandClass, LongRangeChannel, MAX_NODES, type MaybeNotKnown, @@ -1144,7 +1144,7 @@ export class ZWaveController const apiCaps = await this.driver.sendMessage< GetSerialApiCapabilitiesResponse >( - new GetSerialApiCapabilitiesRequest(this.driver), + new GetSerialApiCapabilitiesRequest(), { supportCheck: false, }, @@ -1175,7 +1175,7 @@ export class ZWaveController const version = await this.driver.sendMessage< GetControllerVersionResponse >( - new GetControllerVersionRequest(this.driver), + new GetControllerVersionRequest(), { supportCheck: false, }, @@ -1196,7 +1196,7 @@ export class ZWaveController const protocol = await this.driver.sendMessage< GetProtocolVersionResponse >( - new GetProtocolVersionRequest(this.driver), + new GetProtocolVersionRequest(), ); this._protocolVersion = protocol.protocolVersion; @@ -1236,7 +1236,7 @@ export class ZWaveController const setupCaps = await this.driver.sendMessage< SerialAPISetup_GetSupportedCommandsResponse >( - new SerialAPISetup_GetSupportedCommandsRequest(this.driver), + new SerialAPISetup_GetSupportedCommandsRequest(), ); this._supportedSerialAPISetupCommands = setupCaps.supportedCommands; this.driver.controllerLog.print( @@ -1689,7 +1689,7 @@ export class ZWaveController public async identify(): Promise { this.driver.controllerLog.print(`querying controller IDs...`); const ids = await this.driver.sendMessage( - new GetControllerIdRequest(this.driver), + new GetControllerIdRequest(), { supportCheck: false }, ); this._homeId = ids.homeId; @@ -1716,7 +1716,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< SerialAPISetup_SetTXStatusReportResponse >( - new SerialAPISetup_SetTXStatusReportRequest(this.driver, { + new SerialAPISetup_SetTXStatusReportRequest({ enabled: true, }), ); @@ -1730,7 +1730,7 @@ export class ZWaveController // find the SUC this.driver.controllerLog.print(`finding SUC...`); const suc = await this.driver.sendMessage( - new GetSUCNodeIdRequest(this.driver), + new GetSUCNodeIdRequest(), { supportCheck: false }, ); this._sucNodeId = suc.sucNodeId; @@ -1792,7 +1792,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< SetSerialApiTimeoutsResponse >( - new SetSerialApiTimeoutsRequest(this.driver, { + new SetSerialApiTimeoutsRequest({ ackTimeout: ack, byteTimeout: byte, }), @@ -1926,7 +1926,7 @@ export class ZWaveController const nodesResponse = await this.driver.sendMessage< GetLongRangeNodesResponse >( - new GetLongRangeNodesRequest(this.driver, { + new GetLongRangeNodesRequest({ segmentNumber: segment, }), ); @@ -1946,7 +1946,7 @@ export class ZWaveController public async setControllerNIF(): Promise { this.driver.controllerLog.print("Updating the controller NIF..."); await this.driver.sendMessage( - new SetApplicationNodeInformationRequest(this.driver, { + new SetApplicationNodeInformationRequest({ isListening: true, ...determineNIF(), }), @@ -1978,7 +1978,7 @@ export class ZWaveController } this.driver.controllerLog.print("performing hard reset..."); - await this.driver.sendMessage(new HardResetRequest(this.driver), { + await this.driver.sendMessage(new HardResetRequest(), { supportCheck: false, }); @@ -2003,7 +2003,7 @@ export class ZWaveController try { this.driver.controllerLog.print("Shutting down the Z-Wave API..."); const response = await this.driver.sendMessage( - new ShutdownRequest(this.driver), + new ShutdownRequest(), ); if (response.success) { this.driver.controllerLog.print("Z-Wave API was shut down"); @@ -2036,7 +2036,7 @@ export class ZWaveController "Starting hardware watchdog...", ); await this.driver.sendMessage( - new StartWatchdogRequest(this.driver), + new StartWatchdogRequest(), ); return true; @@ -2063,7 +2063,7 @@ export class ZWaveController "Stopping hardware watchdog...", ); await this.driver.sendMessage( - new StopWatchdogRequest(this.driver), + new StopWatchdogRequest(), ); return true; @@ -2159,7 +2159,7 @@ export class ZWaveController // kick off the inclusion process await this.driver.sendMessage( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ addNodeType: AddNodeType.Any, highPower: true, networkWide: true, @@ -2230,7 +2230,7 @@ export class ZWaveController ); await this.driver.sendMessage( - new AddNodeDSKToNetworkRequest(this.driver, { + new AddNodeDSKToNetworkRequest({ nwiHomeId: nwiHomeIdFromDSK(dskBuffer), authHomeId: authHomeIdFromDSK(dskBuffer), protocol, @@ -2255,7 +2255,7 @@ export class ZWaveController */ public async stopInclusionNoCallback(): Promise { await this.driver.sendMessage( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ callbackId: 0, // disable callbacks addNodeType: AddNodeType.Stop, highPower: true, @@ -2276,7 +2276,7 @@ export class ZWaveController const response = await this.driver.sendMessage< AddNodeToNetworkRequestStatusReport >( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ addNodeType: AddNodeType.Stop, highPower: true, networkWide: true, @@ -2310,7 +2310,7 @@ export class ZWaveController try { // stop the inclusion process await this.driver.sendMessage( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ addNodeType: AddNodeType.Stop, highPower: true, networkWide: true, @@ -2364,7 +2364,7 @@ export class ZWaveController ); try { await this.driver.sendMessage( - new EnableSmartStartListenRequest(this.driver, {}), + new EnableSmartStartListenRequest({}), ); this.driver.controllerLog.print( `Smart Start listening mode enabled`, @@ -2408,7 +2408,7 @@ export class ZWaveController ); try { await this.driver.sendMessage( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ callbackId: 0, // disable callbacks addNodeType: AddNodeType.Stop, highPower: true, @@ -2450,7 +2450,7 @@ export class ZWaveController ); try { await this.driver.sendMessage( - new AddNodeToNetworkRequest(this.driver, { + new AddNodeToNetworkRequest({ callbackId: 0, // disable callbacks addNodeType: AddNodeType.Stop, highPower: true, @@ -2505,7 +2505,7 @@ export class ZWaveController try { // kick off the inclusion process await this.driver.sendMessage( - new RemoveNodeFromNetworkRequest(this.driver, { + new RemoveNodeFromNetworkRequest({ removeNodeType: RemoveNodeType.Any, highPower: true, networkWide: true, @@ -2542,7 +2542,7 @@ export class ZWaveController */ public async stopExclusionNoCallback(): Promise { await this.driver.sendMessage( - new RemoveNodeFromNetworkRequest(this.driver, { + new RemoveNodeFromNetworkRequest({ callbackId: 0, // disable callbacks removeNodeType: RemoveNodeType.Stop, highPower: true, @@ -2567,7 +2567,7 @@ export class ZWaveController try { // kick off the inclusion process await this.driver.sendMessage( - new RemoveNodeFromNetworkRequest(this.driver, { + new RemoveNodeFromNetworkRequest({ removeNodeType: RemoveNodeType.Stop, highPower: true, networkWide: true, @@ -5111,7 +5111,8 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetSUCNodeIdRequest(this.driver, { + new SetSUCNodeIdRequest({ + ownNodeId: this.ownNodeId!, sucNodeId: nodeId, enableSUC, enableSIS, @@ -5166,9 +5167,17 @@ export class ZWaveController await this.deleteSUCReturnRoutes(nodeId); try { + // Some controllers have a bug where they incorrectly respond + // to an AssignSUCReturnRouteRequest with DeleteSUCReturnRoute + const disableCallbackFunctionTypeCheck = !!this.driver + .getDeviceConfig?.(this.ownNodeId!) + ?.compat + ?.disableCallbackFunctionTypeCheck + ?.includes(FunctionType.AssignSUCReturnRoute); const result = await this.driver.sendMessage( - new AssignSUCReturnRouteRequest(this.driver, { + new AssignSUCReturnRouteRequest({ nodeId, + disableCallbackFunctionTypeCheck, }), ); @@ -5280,7 +5289,7 @@ export class ZWaveController // We are always listening const targetWakeup = false; - const cc = new ZWaveProtocolCCAssignSUCReturnRoute(this.driver, { + const cc = new ZWaveProtocolCCAssignSUCReturnRoute({ nodeId, // Empty routes are marked with a nodeId of 0 destinationNodeId: isEmpty ? 0 : this.ownNodeId ?? 1, @@ -5308,14 +5317,11 @@ export class ZWaveController // If a priority route was passed, tell the node to use it if (priorityRouteIndex >= 0) { - const cc = new ZWaveProtocolCCAssignSUCReturnRoutePriority( - this.driver, - { - nodeId, - targetNodeId: this.ownNodeId ?? 1, - routeNumber: priorityRouteIndex, - }, - ); + const cc = new ZWaveProtocolCCAssignSUCReturnRoutePriority({ + nodeId, + targetNodeId: this.ownNodeId ?? 1, + routeNumber: priorityRouteIndex, + }); try { await this.driver.sendZWaveProtocolCC(cc); } catch { @@ -5366,9 +5372,17 @@ export class ZWaveController }); try { + // Some controllers have a bug where they incorrectly respond + // to an DeleteSUCReturnRouteRequest with a different function type + const disableCallbackFunctionTypeCheck = !!this.driver + .getDeviceConfig?.(this.ownNodeId!) + ?.compat + ?.disableCallbackFunctionTypeCheck + ?.includes(FunctionType.DeleteSUCReturnRoute); const result = await this.driver.sendMessage( - new DeleteSUCReturnRouteRequest(this.driver, { + new DeleteSUCReturnRouteRequest({ nodeId, + disableCallbackFunctionTypeCheck, }), ); @@ -5479,7 +5493,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignReturnRouteRequestTransmitReport >( - new AssignReturnRouteRequest(this.driver, { + new AssignReturnRouteRequest({ nodeId, destinationNodeId, }), @@ -5585,7 +5599,7 @@ export class ZWaveController ? this.nodes.get(destinationNodeId)?.isFrequentListening : undefined; - const cc = new ZWaveProtocolCCAssignReturnRoute(this.driver, { + const cc = new ZWaveProtocolCCAssignReturnRoute({ nodeId, // Empty routes are marked with a nodeId of 0 destinationNodeId: isEmpty ? 0 : destinationNodeId, @@ -5613,14 +5627,11 @@ export class ZWaveController // If a priority route was passed, tell the node to use it if (priorityRouteIndex >= 0) { - const cc = new ZWaveProtocolCCAssignReturnRoutePriority( - this.driver, - { - nodeId, - targetNodeId: destinationNodeId, - routeNumber: priorityRouteIndex, - }, - ); + const cc = new ZWaveProtocolCCAssignReturnRoutePriority({ + nodeId, + targetNodeId: destinationNodeId, + routeNumber: priorityRouteIndex, + }); try { await this.driver.sendZWaveProtocolCC(cc); } catch { @@ -5692,7 +5703,7 @@ export class ZWaveController const result = await this.driver.sendMessage< DeleteReturnRouteRequestTransmitReport >( - new DeleteReturnRouteRequest(this.driver, { + new DeleteReturnRouteRequest({ nodeId, }), ); @@ -5748,7 +5759,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignReturnRouteRequestTransmitReport >( - new AssignPriorityReturnRouteRequest(this.driver, { + new AssignPriorityReturnRouteRequest({ nodeId, destinationNodeId, repeaters, @@ -5865,7 +5876,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignPrioritySUCReturnRouteRequestTransmitReport >( - new AssignPrioritySUCReturnRouteRequest(this.driver, { + new AssignPrioritySUCReturnRouteRequest({ nodeId, repeaters, routeSpeed, @@ -5970,7 +5981,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetPriorityRouteRequest(this.driver, { + new SetPriorityRouteRequest({ destinationNodeId, repeaters, routeSpeed, @@ -6007,7 +6018,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetPriorityRouteRequest(this.driver, { + new SetPriorityRouteRequest({ destinationNodeId, // no repeaters = remove }), @@ -6049,7 +6060,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetPriorityRouteResponse >( - new GetPriorityRouteRequest(this.driver, { + new GetPriorityRouteRequest({ destinationNodeId, }), ); @@ -6301,7 +6312,7 @@ export class ZWaveController */ public async isFailedNode(nodeId: number): Promise { const result = await this.driver.sendMessage( - new IsFailedNodeRequest(this.driver, { failedNodeId: nodeId }), + new IsFailedNodeRequest({ failedNodeId: nodeId }), ); return result.result; } @@ -6346,7 +6357,7 @@ export class ZWaveController const result = await this.driver.sendMessage< RemoveFailedNodeRequestStatusReport | RemoveFailedNodeResponse - >(new RemoveFailedNodeRequest(this.driver, { failedNodeId: nodeId })); + >(new RemoveFailedNodeRequest({ failedNodeId: nodeId })); if (result instanceof RemoveFailedNodeResponse) { // This implicates that the process was unsuccessful. @@ -6455,7 +6466,7 @@ export class ZWaveController this._inclusionOptions = options; const result = await this.driver.sendMessage( - new ReplaceFailedNodeRequest(this.driver, { + new ReplaceFailedNodeRequest({ failedNodeId: nodeId, }), ); @@ -6528,7 +6539,7 @@ export class ZWaveController const result = await this.driver.sendMessage< | SerialAPISetup_SetRFRegionResponse | SerialAPISetup_CommandUnsupportedResponse - >(new SerialAPISetup_SetRFRegionRequest(this.driver, { region })); + >(new SerialAPISetup_SetRFRegionRequest({ region })); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { throw new ZWaveError( `Your hardware does not support setting the RF region!`, @@ -6546,7 +6557,7 @@ export class ZWaveController const result = await this.driver.sendMessage< | SerialAPISetup_GetRFRegionResponse | SerialAPISetup_CommandUnsupportedResponse - >(new SerialAPISetup_GetRFRegionRequest(this.driver)); + >(new SerialAPISetup_GetRFRegionRequest()); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { throw new ZWaveError( `Your hardware does not support getting the RF region!`, @@ -6566,7 +6577,7 @@ export class ZWaveController const result = await this.driver.sendMessage< | SerialAPISetup_GetSupportedRegionsResponse | SerialAPISetup_CommandUnsupportedResponse - >(new SerialAPISetup_GetSupportedRegionsRequest(this.driver)); + >(new SerialAPISetup_GetSupportedRegionsRequest()); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { throw new ZWaveError( `Your hardware does not support getting the supported RF regions!`, @@ -6592,7 +6603,7 @@ export class ZWaveController const result = await this.driver.sendMessage< | SerialAPISetup_GetRegionInfoResponse | SerialAPISetup_CommandUnsupportedResponse - >(new SerialAPISetup_GetRegionInfoRequest(this.driver, { region })); + >(new SerialAPISetup_GetRegionInfoRequest({ region })); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { throw new ZWaveError( `Your hardware does not support getting the RF region info!`, @@ -6682,15 +6693,12 @@ export class ZWaveController SerialAPISetupCommand.SetPowerlevel16Bit, ) ) { - request = new SerialAPISetup_SetPowerlevel16BitRequest( - this.driver, - { - powerlevel, - measured0dBm, - }, - ); + request = new SerialAPISetup_SetPowerlevel16BitRequest({ + powerlevel, + measured0dBm, + }); } else { - request = new SerialAPISetup_SetPowerlevelRequest(this.driver, { + request = new SerialAPISetup_SetPowerlevelRequest({ powerlevel, measured0dBm, }); @@ -6724,9 +6732,9 @@ export class ZWaveController SerialAPISetupCommand.GetPowerlevel16Bit, ) ) { - request = new SerialAPISetup_GetPowerlevel16BitRequest(this.driver); + request = new SerialAPISetup_GetPowerlevel16BitRequest(); } else { - request = new SerialAPISetup_GetPowerlevelRequest(this.driver); + request = new SerialAPISetup_GetPowerlevelRequest(); } const result = await this.driver.sendMessage< | SerialAPISetup_GetPowerlevelResponse @@ -6747,10 +6755,9 @@ export class ZWaveController public async setMaxLongRangePowerlevel( limit: number, ): Promise { - const request = new SerialAPISetup_SetLongRangeMaximumTxPowerRequest( - this.driver, - { limit }, - ); + const request = new SerialAPISetup_SetLongRangeMaximumTxPowerRequest({ + limit, + }); const result = await this.driver.sendMessage< | SerialAPISetup_SetLongRangeMaximumTxPowerResponse @@ -6772,9 +6779,7 @@ export class ZWaveController /** Request the maximum TX powerlevel setting for Z-Wave Long Range */ public async getMaxLongRangePowerlevel(): Promise { - const request = new SerialAPISetup_GetLongRangeMaximumTxPowerRequest( - this.driver, - ); + const request = new SerialAPISetup_GetLongRangeMaximumTxPowerRequest(); const result = await this.driver.sendMessage< | SerialAPISetup_GetLongRangeMaximumTxPowerResponse | SerialAPISetup_CommandUnsupportedResponse @@ -6813,10 +6818,7 @@ export class ZWaveController const result = await this.driver.sendMessage< SetLongRangeChannelResponse >( - new SetLongRangeChannelRequest( - this.driver, - { channel }, - ), + new SetLongRangeChannelRequest({ channel }), ); if (result.success) { @@ -6832,7 +6834,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetLongRangeChannelResponse >( - new GetLongRangeChannelRequest(this.driver), + new GetLongRangeChannelRequest(), ); const channel = result.autoChannelSelectionActive @@ -6865,7 +6867,7 @@ export class ZWaveController | SerialAPISetup_SetNodeIDTypeResponse | SerialAPISetup_CommandUnsupportedResponse >( - new SerialAPISetup_SetNodeIDTypeRequest(this.driver, { + new SerialAPISetup_SetNodeIDTypeRequest({ nodeIdType, }), ); @@ -6911,7 +6913,7 @@ export class ZWaveController const result = await this.driver.sendMessage< | SerialAPISetup_GetMaximumPayloadSizeResponse | SerialAPISetup_CommandUnsupportedResponse - >(new SerialAPISetup_GetMaximumPayloadSizeRequest(this.driver), { + >(new SerialAPISetup_GetMaximumPayloadSizeRequest(), { supportCheck: false, }); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { @@ -6932,9 +6934,7 @@ export class ZWaveController | SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse | SerialAPISetup_CommandUnsupportedResponse >( - new SerialAPISetup_GetLongRangeMaximumPayloadSizeRequest( - this.driver, - ), + new SerialAPISetup_GetLongRangeMaximumPayloadSizeRequest(), ); if (result instanceof SerialAPISetup_CommandUnsupportedResponse) { throw new ZWaveError( @@ -6977,7 +6977,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< RequestNodeNeighborUpdateReport >( - new RequestNodeNeighborUpdateRequest(this.driver, { + new RequestNodeNeighborUpdateRequest({ nodeId, discoveryTimeout, }), @@ -7016,7 +7016,7 @@ export class ZWaveController }); try { const resp = await this.driver.sendMessage( - new GetRoutingInfoRequest(this.driver, { + new GetRoutingInfoRequest({ nodeId, removeBadLinks: false, removeNonRepeaters: onlyRepeaters, @@ -7065,7 +7065,7 @@ export class ZWaveController const initData = await this.driver.sendMessage< GetSerialApiInitDataResponse >( - new GetSerialApiInitDataRequest(this.driver), + new GetSerialApiInitDataRequest(), ); this.driver.controllerLog.print( @@ -7121,7 +7121,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetControllerCapabilitiesResponse >( - new GetControllerCapabilitiesRequest(this.driver), + new GetControllerCapabilitiesRequest(), { supportCheck: false }, ); @@ -7183,7 +7183,7 @@ export class ZWaveController `Turning RF ${enabled ? "on" : "off"}...`, ); const ret = await this.driver.sendMessage( - new SetRFReceiveModeRequest(this.driver, { enabled }), + new SetRFReceiveModeRequest({ enabled }), ); return ret.isOK(); } catch (e) { @@ -7226,7 +7226,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< FirmwareUpdateNVM_InitResponse >( - new FirmwareUpdateNVM_InitRequest(this.driver), + new FirmwareUpdateNVM_InitRequest(), ); return ret.supported; } @@ -7240,7 +7240,7 @@ export class ZWaveController value: boolean = true, ): Promise { await this.driver.sendMessage( - new FirmwareUpdateNVM_SetNewImageRequest(this.driver, { + new FirmwareUpdateNVM_SetNewImageRequest({ newImage: value, }), ); @@ -7255,7 +7255,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< FirmwareUpdateNVM_GetNewImageResponse >( - new FirmwareUpdateNVM_GetNewImageRequest(this.driver), + new FirmwareUpdateNVM_GetNewImageRequest(), ); return ret.newImage; } @@ -7273,7 +7273,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< FirmwareUpdateNVM_UpdateCRC16Response >( - new FirmwareUpdateNVM_UpdateCRC16Request(this.driver, { + new FirmwareUpdateNVM_UpdateCRC16Request({ offset, blockLength, crcSeed, @@ -7292,7 +7292,7 @@ export class ZWaveController buffer: Buffer, ): Promise { await this.driver.sendMessage( - new FirmwareUpdateNVM_WriteRequest(this.driver, { + new FirmwareUpdateNVM_WriteRequest({ offset, buffer, }), @@ -7308,7 +7308,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< FirmwareUpdateNVM_IsValidCRC16Response >( - new FirmwareUpdateNVM_IsValidCRC16Request(this.driver), + new FirmwareUpdateNVM_IsValidCRC16Request(), ); return ret.isValid; } @@ -7320,7 +7320,7 @@ export class ZWaveController */ public async getNVMId(): Promise { const ret = await this.driver.sendMessage( - new GetNVMIdRequest(this.driver), + new GetNVMIdRequest(), ); return pick(ret, ["nvmManufacturerId", "memoryType", "memorySize"]); } @@ -7332,7 +7332,7 @@ export class ZWaveController */ public async externalNVMReadByte(offset: number): Promise { const ret = await this.driver.sendMessage( - new ExtNVMReadLongByteRequest(this.driver, { offset }), + new ExtNVMReadLongByteRequest({ offset }), ); return ret.byte; } @@ -7351,7 +7351,7 @@ export class ZWaveController data: number, ): Promise { const ret = await this.driver.sendMessage( - new ExtNVMWriteLongByteRequest(this.driver, { offset, byte: data }), + new ExtNVMWriteLongByteRequest({ offset, byte: data }), ); return ret.success; } @@ -7366,7 +7366,7 @@ export class ZWaveController length: number, ): Promise { const ret = await this.driver.sendMessage( - new ExtNVMReadLongBufferRequest(this.driver, { + new ExtNVMReadLongBufferRequest({ offset, length, }), @@ -7386,7 +7386,7 @@ export class ZWaveController length: number, ): Promise<{ buffer: Buffer; endOfFile: boolean }> { const ret = await this.driver.sendMessage( - new NVMOperationsReadRequest(this.driver, { + new NVMOperationsReadRequest({ offset, length, }), @@ -7426,7 +7426,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsReadRequest(this.driver, { + new ExtendedNVMOperationsReadRequest({ offset, length, }), @@ -7472,7 +7472,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtNVMWriteLongBufferResponse >( - new ExtNVMWriteLongBufferRequest(this.driver, { + new ExtNVMWriteLongBufferRequest({ offset, buffer, }), @@ -7495,7 +7495,7 @@ export class ZWaveController buffer: Buffer, ): Promise<{ endOfFile: boolean }> { const ret = await this.driver.sendMessage( - new NVMOperationsWriteRequest(this.driver, { + new NVMOperationsWriteRequest({ offset, buffer, }), @@ -7538,7 +7538,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsWriteRequest(this.driver, { + new ExtendedNVMOperationsWriteRequest({ offset, buffer, }), @@ -7582,7 +7582,7 @@ export class ZWaveController */ public async externalNVMOpen(): Promise { const ret = await this.driver.sendMessage( - new NVMOperationsOpenRequest(this.driver), + new NVMOperationsOpenRequest(), ); if (!ret.isOK()) { throw new ZWaveError( @@ -7607,7 +7607,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsOpenRequest(this.driver), + new ExtendedNVMOperationsOpenRequest(), ); if (!ret.isOK()) { throw new ZWaveError( @@ -7635,7 +7635,7 @@ export class ZWaveController */ public async externalNVMClose(): Promise { const ret = await this.driver.sendMessage( - new NVMOperationsCloseRequest(this.driver), + new NVMOperationsCloseRequest(), ); if (!ret.isOK()) { throw new ZWaveError( @@ -7656,7 +7656,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsCloseRequest(this.driver), + new ExtendedNVMOperationsCloseRequest(), ); if (!ret.isOK()) { throw new ZWaveError( @@ -8090,7 +8090,7 @@ export class ZWaveController rssiChannel3?: RSSI; }> { const ret = await this.driver.sendMessage( - new GetBackgroundRSSIRequest(this.driver), + new GetBackgroundRSSIRequest(), ); const rssi = pick(ret, [ "rssiChannel0", @@ -8810,7 +8810,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ intent: LearnModeIntent.Inclusion, }), ); @@ -8844,7 +8844,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ // TODO: We should be using .Stop here for the non-legacy // inclusion/exclusion, but that command results in a // negative response on current firmwares, even though it works. @@ -8880,7 +8880,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ intent: LearnModeIntent.NetworkWideExclusion, }), ); @@ -8916,7 +8916,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ // TODO: We should be using .Stop here for the non-legacy // inclusion/exclusion, but that command results in a // negative response on current firmwares, even though it works. @@ -9675,7 +9675,7 @@ export class ZWaveController const supportsS2 = supportedCCs.includes(CommandClasses["Security 2"]); let initTimeout: number; - let initPredicate: (cc: ICommandClass) => boolean; + let initPredicate: (cc: CCId) => boolean; // KEX Get must be received: // - no later than 10..30 seconds after the inclusion if S0 is supported diff --git a/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts b/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts index 3c5eb2a7f90a..81c42e810498 100644 --- a/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts +++ b/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts @@ -13,7 +13,6 @@ import { ZWaveErrorCodes, isZWaveError, } from "@zwave-js/core"; -import { type ZWaveHost } from "@zwave-js/host"; import { MessageOrigin } from "@zwave-js/serial"; import { MOCK_FRAME_ACK_TIMEOUT, @@ -23,7 +22,6 @@ import { MockZWaveFrameType, createMockZWaveRequestFrame, } from "@zwave-js/testing"; -import { wait } from "alcalzone-shared/async"; import { ApplicationCommandRequest } from "../serialapi/application/ApplicationCommandRequest"; import { type ApplicationUpdateRequest, @@ -87,17 +85,22 @@ import { import { determineNIF } from "./NodeInformationFrame"; function createLazySendDataPayload( - host: ZWaveHost, controller: MockController, node: MockNode, msg: SendDataRequest | SendDataMulticastRequest, ): () => CommandClass { return () => { try { - const cmd = CommandClass.from(node.host, { - nodeId: controller.host.ownNodeId, + const cmd = CommandClass.from({ + nodeId: controller.ownNodeId, data: msg.payload, origin: MessageOrigin.Host, + context: { + sourceNodeId: node.id, + __internalIsMockNode: true, + ...node.encodingContext, + ...node.securityManagers, + }, }); // Store the command because assertReceivedHostMessage needs it // @ts-expect-error @@ -108,8 +111,8 @@ function createLazySendDataPayload( if (e.code === ZWaveErrorCodes.CC_NotImplemented) { // The whole CC is not implemented yet. If this happens in tests, it is because we sent a raw CC. try { - const cmd = new CommandClass(host, { - nodeId: controller.host.ownNodeId, + const cmd = new CommandClass({ + nodeId: controller.ownNodeId, ccId: msg.payload[0], ccCommand: msg.payload[1], payload: msg.payload.subarray(2), @@ -138,76 +141,76 @@ function createLazySendDataPayload( } const respondToGetControllerId: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetControllerIdRequest) { - const ret = new GetControllerIdResponse(host, { - homeId: host.homeId, - ownNodeId: host.ownNodeId, + const ret = new GetControllerIdResponse({ + homeId: controller.homeId, + ownNodeId: controller.ownNodeId, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToGetSerialApiCapabilities: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetSerialApiCapabilitiesRequest) { - const ret = new GetSerialApiCapabilitiesResponse(host, { + const ret = new GetSerialApiCapabilitiesResponse({ ...controller.capabilities, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToGetControllerVersion: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetControllerVersionRequest) { - const ret = new GetControllerVersionResponse(host, { + const ret = new GetControllerVersionResponse({ ...controller.capabilities, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToGetControllerCapabilities: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetControllerCapabilitiesRequest) { - const ret = new GetControllerCapabilitiesResponse(host, { + const ret = new GetControllerCapabilitiesResponse({ ...controller.capabilities, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToGetSUCNodeId: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetSUCNodeIdRequest) { const sucNodeId = controller.capabilities.isStaticUpdateController - ? host.ownNodeId + ? controller.ownNodeId : controller.capabilities.sucNodeId; - const ret = new GetSUCNodeIdResponse(host, { + const ret = new GetSUCNodeIdResponse({ sucNodeId, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToGetSerialApiInitData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetSerialApiInitDataRequest) { const nodeIds = new Set(controller.nodes.keys()); - nodeIds.add(host.ownNodeId); + nodeIds.add(controller.ownNodeId); - const ret = new GetSerialApiInitDataResponse(host, { + const ret = new GetSerialApiInitDataResponse({ zwaveApiVersion: controller.capabilities.zwaveApiVersion, isPrimary: !controller.capabilities.isSecondary, nodeType: NodeType.Controller, @@ -217,16 +220,16 @@ const respondToGetSerialApiInitData: MockControllerBehavior = { nodeIds: [...nodeIds], zwaveChipType: controller.capabilities.zwaveChipType, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } }, }; const respondToSoftReset: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { if (msg instanceof SoftResetRequest) { - const ret = new SerialAPIStartedRequest(host, { + const ret = new SerialAPIStartedRequest({ wakeUpReason: SerialAPIWakeUpReason.SoftwareReset, watchdogEnabled: controller.capabilities.watchdogEnabled, isListening: true, @@ -234,7 +237,7 @@ const respondToSoftReset: MockControllerBehavior = { supportsLongRange: controller.capabilities.supportsLongRange, }); setImmediate(async () => { - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); }); return true; } @@ -242,10 +245,10 @@ const respondToSoftReset: MockControllerBehavior = { }; const respondToGetNodeProtocolInfo: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof GetNodeProtocolInfoRequest) { - if (msg.requestedNodeId === host.ownNodeId) { - const ret = new GetNodeProtocolInfoResponse(host, { + if (msg.requestedNodeId === controller.ownNodeId) { + const ret = new GetNodeProtocolInfoResponse({ ...determineNIF(), nodeType: NodeType.Controller, isListening: true, @@ -257,16 +260,16 @@ const respondToGetNodeProtocolInfo: MockControllerBehavior = { optionalFunctionality: true, protocolVersion: 3, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } else if (controller.nodes.has(msg.requestedNodeId)) { const nodeCaps = controller.nodes.get( msg.requestedNodeId, )!.capabilities; - const ret = new GetNodeProtocolInfoResponse(host, { + const ret = new GetNodeProtocolInfoResponse({ ...nodeCaps, }); - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); return true; } } @@ -274,7 +277,7 @@ const respondToGetNodeProtocolInfo: MockControllerBehavior = { }; const handleSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof SendDataRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -294,10 +297,10 @@ const handleSendData: MockControllerBehavior = { ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); // We deferred parsing of the CC because it requires the node's host to do so. // Now we can do that. Also set the CC node ID to the controller's own node ID, @@ -305,7 +308,6 @@ const handleSendData: MockControllerBehavior = { const node = controller.nodes.get(msg.getNodeId()!)!; // Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission const lazyPayload = createLazySendDataPayload( - host, controller, node, msg, @@ -345,14 +347,14 @@ const handleSendData: MockControllerBehavior = { MockControllerCommunicationState.Idle, ); - const cb = new SendDataRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new SendDataRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } else { // No callback was requested, we're done controller.state.set( @@ -366,7 +368,7 @@ const handleSendData: MockControllerBehavior = { }; const handleSendDataMulticast: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof SendDataMulticastRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -388,10 +390,10 @@ const handleSendDataMulticast: MockControllerBehavior = { ); // Notify the host that the message was sent - const res = new SendDataMulticastResponse(host, { + const res = new SendDataMulticastResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); // We deferred parsing of the CC because it requires the node's host to do so. // Now we can do that. Also set the CC node ID to the controller's own node ID, @@ -402,7 +404,6 @@ const handleSendDataMulticast: MockControllerBehavior = { const node = controller.nodes.get(nodeId)!; // Create a lazy frame, so it can be deserialized on the node after a short delay to simulate radio transmission const lazyPayload = createLazySendDataPayload( - host, controller, node, msg, @@ -444,14 +445,14 @@ const handleSendDataMulticast: MockControllerBehavior = { MockControllerCommunicationState.Idle, ); - const cb = new SendDataMulticastRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new SendDataMulticastRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } else { // No callback was requested, we're done controller.state.set( @@ -465,7 +466,7 @@ const handleSendDataMulticast: MockControllerBehavior = { }; const handleRequestNodeInfo: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof RequestNodeInfoRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -488,10 +489,9 @@ const handleRequestNodeInfo: MockControllerBehavior = { // Send the data to the node const node = controller.nodes.get(msg.getNodeId()!)!; - const command = new ZWaveProtocolCCRequestNodeInformationFrame( - node.host, - { nodeId: controller.host.ownNodeId }, - ); + const command = new ZWaveProtocolCCRequestNodeInformationFrame({ + nodeId: controller.ownNodeId, + }); const frame = createMockZWaveRequestFrame(command, { ackRequested: false, }); @@ -503,10 +503,10 @@ const handleRequestNodeInfo: MockControllerBehavior = { ); // Notify the host that the message was sent - const res = new RequestNodeInfoResponse(host, { + const res = new RequestNodeInfoResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); // Put the controller into waiting state controller.state.set( @@ -518,28 +518,28 @@ const handleRequestNodeInfo: MockControllerBehavior = { let cb: ApplicationUpdateRequest; try { const nodeInfo = await nodeInfoPromise; - cb = new ApplicationUpdateRequestNodeInfoReceived(host, { + cb = new ApplicationUpdateRequestNodeInfoReceived({ nodeInformation: { ...nodeInfo, nodeId: nodeInfo.nodeId as number, }, }); } catch { - cb = new ApplicationUpdateRequestNodeInfoRequestFailed(host); + cb = new ApplicationUpdateRequestNodeInfoRequestFailed(); } controller.state.set( MockControllerStateKeys.CommunicationState, MockControllerCommunicationState.Idle, ); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); return true; } }, }; const handleAssignSUCReturnRoute: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof AssignSUCReturnRouteRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -564,9 +564,9 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { // Send the command to the node const node = controller.nodes.get(msg.getNodeId()!)!; - const command = new ZWaveProtocolCCAssignSUCReturnRoute(host, { + const command = new ZWaveProtocolCCAssignSUCReturnRoute({ nodeId: node.id, - destinationNodeId: controller.host.ownNodeId, + destinationNodeId: controller.ownNodeId, repeaters: [], // don't care routeIndex: 0, // don't care destinationSpeed: ZWaveDataRate["100k"], @@ -578,10 +578,10 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { const ackPromise = controller.sendToNode(node, frame); // Notify the host that the message was sent - const res = new AssignSUCReturnRouteResponse(host, { + const res = new AssignSUCReturnRouteResponse({ wasExecuted: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); let ack = false; if (expectCallback) { @@ -605,14 +605,14 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { ); if (expectCallback) { - const cb = new AssignSUCReturnRouteRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new AssignSUCReturnRouteRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } return true; } @@ -620,48 +620,42 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { }; const forwardCommandClassesToHost: MockControllerBehavior = { - async onNodeFrame(host, controller, node, frame) { + async onNodeFrame(controller, node, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof CommandClass && !(frame.payload instanceof ZWaveProtocolCC) ) { // This is a CC that is meant for the host application - const msg = new ApplicationCommandRequest(host, { + const msg = new ApplicationCommandRequest({ command: frame.payload, }); // Nodes send commands TO the controller, so we need to fix the node ID before forwarding msg.getNodeId = () => node.id; - // Simulate a serialized frame being transmitted via radio - const data = msg.serialize(); - await wait(node.capabilities.txDelay); - // Then receive it - await controller.sendToHost(data); + // Simulate a serialized frame being transmitted via radio before receiving it + await controller.sendMessageToHost(msg, node); return true; } }, }; const forwardUnsolicitedNIF: MockControllerBehavior = { - async onNodeFrame(host, controller, node, frame) { + async onNodeFrame(controller, node, frame) { if ( frame.type === MockZWaveFrameType.Request && frame.payload instanceof ZWaveProtocolCCNodeInformationFrame ) { - const updateRequest = new ApplicationUpdateRequestNodeInfoReceived( - host, - { - nodeInformation: { - ...frame.payload, - nodeId: frame.payload.nodeId as number, - }, + const updateRequest = new ApplicationUpdateRequestNodeInfoReceived({ + nodeInformation: { + ...frame.payload, + nodeId: frame.payload.nodeId as number, }, + }); + // Simulate a serialized frame being transmitted via radio before receiving it + await controller.sendMessageToHost( + updateRequest, + node, ); - // Simulate a serialized frame being transmitted via radio - const data = updateRequest.serialize(); - await wait(node.capabilities.txDelay); - // Then receive it - await controller.sendToHost(data); return true; } }, diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index ace5128f2ff0..f62bc819f10b 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -1,14 +1,18 @@ import { JsonlDB, type JsonlDBOptions } from "@alcalzone/jsonl-db"; import { + type CCAPIHost, CRC16CC, CRC16CCCommandEncapsulation, type CommandClass, type FirmwareUpdateResult, type ICommandClassContainer, + type InterviewContext, InvalidCC, KEXFailType, MultiChannelCC, + type PersistValuesContext, type Powerlevel, + type RefreshValuesContext, Security2CC, Security2CCCommandsSupportedGet, Security2CCCommandsSupportedReport, @@ -49,15 +53,16 @@ import { externalConfigDir, } from "@zwave-js/config"; import { + type CCId, CommandClasses, ControllerLogger, ControllerRole, ControllerStatus, Duration, EncapsulationFlags, - type ICommandClass, type KeyPair, type LogConfig, + type LogNodeOptions, MAX_SUPERVISION_SESSION_ID, MAX_TRANSPORT_SERVICE_SESSION_ID, MPANState, @@ -109,8 +114,10 @@ import { wasControllerReset, } from "@zwave-js/core"; import type { + CCEncodingContext, + HostIDs, NodeSchedulePollOptions, - ZWaveApplicationHost, + ZWaveHostOptions, } from "@zwave-js/host"; import { type BootloaderChunk, @@ -118,7 +125,9 @@ import { FunctionType, type INodeQuery, Message, + type MessageEncodingContext, MessageHeaders, + type MessageParsingContext, MessageType, type SuccessIndicator, XModemMessageHeaders, @@ -134,7 +143,6 @@ import { } from "@zwave-js/serial"; import { AsyncQueue, - type ReadonlyThrowingMap, type ThrowingMap, TypedEventEmitter, buffer2hex, @@ -203,6 +211,10 @@ import { isTransmitReport, } from "../serialapi/transport/SendDataShared"; +import { type ZWaveNodeBase } from "../node/mixins/00_Base"; +import { type NodeWakeup } from "../node/mixins/30_Wakeup"; +import { type NodeValues } from "../node/mixins/40_Values"; +import { type SchedulePoll } from "../node/mixins/60_ScheduledPoll"; import { SendTestFrameRequest, SendTestFrameTransmitReport, @@ -539,7 +551,7 @@ interface AwaitedThing { type AwaitedMessageHeader = AwaitedThing; type AwaitedMessageEntry = AwaitedThing; -type AwaitedCommandEntry = AwaitedThing; +type AwaitedCommandEntry = AwaitedThing; export type AwaitedBootloaderChunkEntry = AwaitedThing; interface TransportServiceSession { @@ -589,7 +601,11 @@ export type DriverEvents = Extract; * instance or its associated nodes. */ export class Driver extends TypedEventEmitter - implements ZWaveApplicationHost + implements + CCAPIHost, + InterviewContext, + RefreshValuesContext, + PersistValuesContext { public constructor( private port: string | ZWaveSerialPortImplementation, @@ -650,6 +666,31 @@ export class Driver extends TypedEventEmitter this._options.storage.deviceConfigPriorityDir, }); + const self = this; + this.messageParsingContext = { + getHighestSecurityClass: (nodeId) => + this.getHighestSecurityClass(nodeId), + hasSecurityClass: (nodeId, securityClass) => + this.hasSecurityClass(nodeId, securityClass), + setSecurityClass: (nodeId, securityClass, granted) => + this.setSecurityClass(nodeId, securityClass, granted), + getDeviceConfig: (nodeId) => this.getDeviceConfig(nodeId), + get securityManager() { + return self.securityManager; + }, + get securityManager2() { + return self.securityManager2; + }, + get securityManagerLR() { + return self.securityManagerLR; + }, + }; + this.messageEncodingContext = { + ...this.messageParsingContext, + getSupportedCCVersion: (cc, nodeId, endpointIndex) => + this.getSupportedCCVersion(cc, nodeId, endpointIndex), + }; + this.immediateQueue = new TransactionQueue({ name: "immediate", mayStartNextTransaction: (t) => { @@ -684,6 +725,37 @@ export class Driver extends TypedEventEmitter /** The serial port instance */ private serial: ZWaveSerialPortBase | undefined; + private messageParsingContext: Omit< + MessageParsingContext, + keyof HostIDs | "nodeIdType" + >; + private messageEncodingContext: Omit< + MessageEncodingContext, + keyof HostIDs | "nodeIdType" + >; + + private getCCEncodingContext(): MessageEncodingContext & CCEncodingContext { + // FIXME: The type system isn't helping here. We need the security managers to encode CCs + // but not for messages, yet those implicitly encode CCs + return { + ...this.messageEncodingContext, + ownNodeId: this.controller.ownNodeId!, + homeId: this.controller.homeId!, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, + }; + } + + private getCCParsingContext() { + // FIXME: The type system isn't helping here. We need the security managers to decode CCs + // but not for messages, yet those implicitly decode CCs + return { + ...this.messageParsingContext, + ownNodeId: this.controller.ownNodeId!, + homeId: this.controller.homeId!, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, + }; + } + // We have multiple queues to achieve multiple "layers" of communication priority: // The default queue for most messages private queue: TransactionQueue; @@ -795,15 +867,22 @@ export class Driver extends TypedEventEmitter } private _controllerLog: ControllerLogger; - /** - * **!!! INTERNAL !!!** - * - * Not intended to be used by applications - */ + /** @internal */ public get controllerLog(): ControllerLogger { return this._controllerLog; } + public logNode( + nodeId: number, + message: string, + level?: LogNodeOptions["level"], + ): void; + public logNode(nodeId: number, options: LogNodeOptions): void; + public logNode(...args: any[]): void { + // @ts-expect-error + this._controllerLog.logNode(...args); + } + private _controller: ZWaveController | undefined; /** Encapsulates information about the Z-Wave controller and provides access to its nodes */ public get controller(): ZWaveController { @@ -920,21 +999,22 @@ export class Driver extends TypedEventEmitter return this.controller.ownNodeId!; } - public get nodeIdType(): NodeIDType { - return this._controller?.nodeIdType ?? NodeIDType.Short; + /** @internal Used for compatibility with the CCAPIHost interface */ + public getNode(nodeId: number): ZWaveNode | undefined { + return this.controller.nodes.get(nodeId); } - /** - * **!!! INTERNAL !!!** - * - * Not intended to be used by applications. Use `controller.nodes` instead! - */ - public get nodes(): ReadonlyThrowingMap { - // This is needed for the ZWaveHost interface - return this.controller.nodes; + /** @internal Used for compatibility with the CCAPIHost interface */ + public getNodeOrThrow(nodeId: number): ZWaveNode { + return this.controller.nodes.getOrThrow(nodeId); + } + + /** @internal Used for compatibility with the CCAPIHost interface */ + public getAllNodes(): ZWaveNode[] { + return [...this.controller.nodes.values()]; } - public getNodeUnsafe(msg: Message): ZWaveNode | undefined { + public tryGetNode(msg: Message): ZWaveNode | undefined { const nodeId = msg.getNodeId(); if (nodeId != undefined) return this.controller.nodes.get(nodeId); } @@ -974,6 +1054,10 @@ export class Driver extends TypedEventEmitter return this.controller.nodes.get(nodeId)?.deviceConfig; } + public lookupManufacturer(manufacturerId: number): string | undefined { + return this.configManager.lookupManufacturer(manufacturerId); + } + public getHighestSecurityClass( nodeId: number, ): MaybeNotKnown { @@ -1006,16 +1090,6 @@ export class Driver extends TypedEventEmitter node.setSecurityClass(securityClass, granted); } - /** - * **!!! INTERNAL !!!** - * - * Not intended to be used by applications. Use `node.isControllerNode` instead! - */ - public isControllerNode(nodeId: number): boolean { - // This is needed for the ZWaveHost interface - return nodeId === this.ownNodeId; - } - /** Updates the logging configuration without having to restart the driver. */ public updateLogConfig(config: Partial): void { this._logContainer.updateConfiguration(config); @@ -1036,6 +1110,33 @@ export class Driver extends TypedEventEmitter ); } + /** + * **!!! INTERNAL !!!** + * + * Not intended to be used by applications + */ + public getUserPreferences(): ZWaveHostOptions["preferences"] { + return this._options.preferences; + } + + /** + * **!!! INTERNAL !!!** + * + * Not intended to be used by applications + */ + public getInterviewOptions(): ZWaveHostOptions["interview"] { + return this._options.interview; + } + + /** + * **!!! INTERNAL !!!** + * + * Not intended to be used by applications + */ + public getCommunicationTimeouts(): ZWaveHostOptions["timeouts"] { + return this._options.timeouts; + } + /** * Enumerates all existing serial ports. * @param local Whether to include local serial ports @@ -2687,7 +2788,7 @@ export class Driver extends TypedEventEmitter }; /** Checks if there are any pending messages for the given node */ - private hasPendingMessages(node: ZWaveNode): boolean { + private hasPendingMessages(node: ZWaveNodeBase & SchedulePoll): boolean { // First check if there are messages in the queue if ( this.hasPendingTransactions( @@ -2698,7 +2799,7 @@ export class Driver extends TypedEventEmitter } // Then check if there are scheduled polls - return node.scheduledPolls.size > 0; + return node.hasScheduledPolls(); } /** Checks if there are any pending transactions that match the given predicate */ @@ -2741,7 +2842,7 @@ export class Driver extends TypedEventEmitter /** * Retrieves the maximum version of a command class that can be used to communicate with a node. * Returns the highest implemented version if the node's CC version is unknown. - * Throws if the CC is not implemented in this library yet. + * Returns `undefined` for CCs that are not implemented in this library yet. * * @param cc The command class whose version should be retrieved * @param nodeId The node for which the CC version should be retrieved @@ -2751,7 +2852,14 @@ export class Driver extends TypedEventEmitter cc: CommandClasses, nodeId: number, endpointIndex: number = 0, - ): number { + ): number | undefined { + const implementedVersion = getImplementedVersion(cc); + if ( + implementedVersion === 0 + || implementedVersion === Number.POSITIVE_INFINITY + ) { + return undefined; + } const supportedVersion = this.getSupportedCCVersion( cc, nodeId, @@ -2759,28 +2867,10 @@ export class Driver extends TypedEventEmitter ); if (supportedVersion === 0) { // Unknown, use the highest implemented version - const implementedVersion = getImplementedVersion(cc); - if ( - implementedVersion !== 0 - && implementedVersion !== Number.POSITIVE_INFINITY - ) { - return implementedVersion; - } - } else { - // For supported versions find the maximum version supported by both the - // node and this library - const implementedVersion = getImplementedVersion(cc); - if ( - implementedVersion !== 0 - && implementedVersion !== Number.POSITIVE_INFINITY - ) { - return Math.min(supportedVersion, implementedVersion); - } + return implementedVersion; } - throw new ZWaveError( - "Cannot retrieve the version of a CC that is not implemented", - ZWaveErrorCodes.CC_NotImplemented, - ); + + return Math.min(supportedVersion, implementedVersion); } /** @@ -2848,14 +2938,14 @@ export class Driver extends TypedEventEmitter /** * **!!! INTERNAL !!!** * - * Not intended to be used by applications + * Not intended to be used by applications. + * Needed for compatibility with CCAPIHost */ public schedulePoll( nodeId: number, valueId: ValueID, options: NodeSchedulePollOptions, ): boolean { - // Needed for ZWaveApplicationHost const node = this.controller.nodes.getOrThrow(nodeId); return node.schedulePoll(valueId, options); } @@ -2950,7 +3040,7 @@ export class Driver extends TypedEventEmitter try { this.isSoftResetting = true; - await this.sendMessage(new SoftResetRequest(this), { + await this.sendMessage(new SoftResetRequest(), { supportCheck: false, pauseSendThread: true, }); @@ -3010,7 +3100,7 @@ export class Driver extends TypedEventEmitter try { this.isSoftResetting = true; - await this.sendMessage(new SoftResetRequest(this), { + await this.sendMessage(new SoftResetRequest(), { supportCheck: false, pauseSendThread: true, }); @@ -3102,7 +3192,7 @@ export class Driver extends TypedEventEmitter try { // And resume sending - this requires us to unpause the send thread this.unpauseSendQueue(); - await this.sendMessage(new GetControllerVersionRequest(this), { + await this.sendMessage(new GetControllerVersionRequest(), { supportCheck: false, priority: MessagePriority.ControllerImmediate, }); @@ -3411,13 +3501,14 @@ export class Driver extends TypedEventEmitter try { // Parse the message while remembering potential decoding errors in embedded CCs // This way we can log the invalid CC contents - msg = Message.from(this, { + msg = Message.from({ data, sdkVersion: this._controller?.sdkVersion, + ctx: this.getCCParsingContext(), }, this._requestContext); if (isCommandClassContainer(msg)) { // Whether successful or not, a message from a node should update last seen - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); if (node) node.lastSeen = new Date(); // Ensure there are no errors @@ -3426,7 +3517,7 @@ export class Driver extends TypedEventEmitter // And update statistics if (!!this._controller) { if (isCommandClassContainer(msg)) { - this.getNodeUnsafe(msg)?.incrementStatistics("commandsRX"); + this.tryGetNode(msg)?.incrementStatistics("commandsRX"); } else { this._controller.incrementStatistics("messagesRX"); } @@ -3442,7 +3533,7 @@ export class Driver extends TypedEventEmitter if (response) await this.writeHeader(response); if (!!this._controller) { if (isCommandClassContainer(msg)) { - this.getNodeUnsafe(msg)?.incrementStatistics( + this.tryGetNode(msg)?.incrementStatistics( "commandsDroppedRX", ); @@ -3454,7 +3545,7 @@ export class Driver extends TypedEventEmitter && msg.command instanceof InvalidCC ) { // If it was, we need to notify the sender that we couldn't decode the command - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); if (node) { const endpoint = node.getEndpoint( msg.command.endpointIndex, @@ -3522,7 +3613,7 @@ export class Driver extends TypedEventEmitter msg.command instanceof SecurityCCCommandEncapsulationNonceGet ) { - void this.getNodeUnsafe(msg)?.handleSecurityNonceGet(); + void this.tryGetNode(msg)?.handleSecurityNonceGet(); } // Transport Service commands must be handled before assembling partial CCs @@ -3738,7 +3829,7 @@ export class Driver extends TypedEventEmitter if (!encapS2) return false; // With the MGRP extension present - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); if (!node) return false; const groupId = encapS2.getMulticastGroupId(); if (groupId == undefined) return false; @@ -3923,7 +4014,7 @@ export class Driver extends TypedEventEmitter }, error: ZWaveError, ): boolean { - const node = this.getNodeUnsafe(transaction.message); + const node = this.tryGetNode(transaction.message); if (!node) return false; // This should never happen, but whatever const messagePart1 = isSendData(transaction.message) @@ -4088,7 +4179,7 @@ export class Driver extends TypedEventEmitter || this._recoveryPhase === ControllerRecoveryPhase.CallbackTimeoutAfterReset ) { - const node = this.getNodeUnsafe(transaction.message); + const node = this.tryGetNode(transaction.message); if (!node) return false; // This should never happen, but whatever // The controller is still timing out transmitting after a soft reset, don't try again. @@ -4376,7 +4467,10 @@ export class Driver extends TypedEventEmitter // this is the final one, merge the previous responses this.partialCCSessions.delete(partialSessionKey!); try { - command.mergePartialCCs(this, session); + command.mergePartialCCs(session, { + sourceNodeId: msg.command.nodeId as number, + ...this.getCCParsingContext(), + }); // Ensure there are no errors assertValidCCs(msg); } catch (e) { @@ -4465,7 +4559,7 @@ export class Driver extends TypedEventEmitter level: "debug", direction: "outbound", }); - const cc = new TransportServiceCCSegmentRequest(this, { + const cc = new TransportServiceCCSegmentRequest({ nodeId: command.nodeId, sessionId: command.sessionId, datagramOffset: offset, @@ -4482,7 +4576,7 @@ export class Driver extends TypedEventEmitter level: "debug", direction: "outbound", }); - const cc = new TransportServiceCCSegmentComplete(this, { + const cc = new TransportServiceCCSegmentComplete({ nodeId: command.nodeId, sessionId: command.sessionId, }); @@ -4530,7 +4624,7 @@ export class Driver extends TypedEventEmitter }); } else { // This belongs to a session we don't know... tell the sending node to try again - const cc = new TransportServiceCCSegmentWait(this, { + const cc = new TransportServiceCCSegmentWait({ nodeId: command.nodeId, pendingSegments: 0, }); @@ -4893,7 +4987,7 @@ ${handlers.length} left`, let handlers: RequestHandlerEntry[] | undefined; if (isNodeQuery(msg) || isCommandClassContainer(msg)) { - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); if (node) { // We have received an unsolicited message from a dead node, bring it back to life if (node.status === NodeStatus.Dead) { @@ -5178,17 +5272,27 @@ ${handlers.length} left`, // 3. if (SupervisionCC.requiresEncapsulation(cmd)) { - cmd = SupervisionCC.encapsulate(this, cmd); + cmd = SupervisionCC.encapsulate( + cmd, + this.getNextSupervisionSessionId(cmd.nodeId as number), + ); } // 4. if (MultiChannelCC.requiresEncapsulation(cmd)) { - cmd = MultiChannelCC.encapsulate(this, cmd); + const multiChannelCCVersion = this.getSupportedCCVersion( + CommandClasses["Multi Channel"], + cmd.nodeId as number, + ); + + cmd = multiChannelCCVersion === 1 + ? MultiChannelCC.encapsulateV1(cmd) + : MultiChannelCC.encapsulate(cmd); } // 5. if (CRC16CC.requiresEncapsulation(cmd)) { - cmd = CRC16CC.encapsulate(this, cmd); + cmd = CRC16CC.encapsulate(cmd); } else { // The command must be S2-encapsulated, if ... let maybeS2 = false; @@ -5204,17 +5308,26 @@ ${handlers.length} left`, maybeS2 = true; } if (maybeS2 && Security2CC.requiresEncapsulation(cmd)) { - cmd = Security2CC.encapsulate(this, cmd, { - securityClass: options.s2OverrideSecurityClass, - multicastOutOfSync: !!options.s2MulticastOutOfSync, - multicastGroupId: options.s2MulticastGroupId, - verifyDelivery: options.s2VerifyDelivery, - }); + cmd = Security2CC.encapsulate( + cmd, + this.ownNodeId, + this, + { + securityClass: options.s2OverrideSecurityClass, + multicastOutOfSync: !!options.s2MulticastOutOfSync, + multicastGroupId: options.s2MulticastGroupId, + verifyDelivery: options.s2VerifyDelivery, + }, + ); } // This check will return false for S2-encapsulated commands if (SecurityCC.requiresEncapsulation(cmd)) { - cmd = SecurityCC.encapsulate(this, cmd); + cmd = SecurityCC.encapsulate( + this.ownNodeId, + this.securityManager!, + cmd, + ); } } return cmd; @@ -5272,7 +5385,7 @@ ${handlers.length} left`, // Do not persist values for a node or endpoint that does not exist const endpoint = this.tryGetEndpoint(cc); - const node = endpoint?.getNodeUnsafe(); + const node = endpoint?.tryGetNode(); if (!node) return false; // Do not persist values for a CC that was force-removed via config @@ -5321,7 +5434,7 @@ ${handlers.length} left`, result: Message | undefined, ): void { // Update statistics - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); let success = true; if (isSendData(msg) || isNodeQuery(msg)) { // This shouldn't happen, but just in case @@ -5422,7 +5535,7 @@ ${handlers.length} left`, } const message = transaction.message; - const targetNode = message.getNodeUnsafe(this); + const targetNode = message.tryGetNode(this); // Messages to the controller may always be sent... if (!targetNode) return true; @@ -5697,8 +5810,14 @@ ${handlers.length} left`, msg: Message, transactionSource?: string, ): Promise { + // Give the command a callback ID if it needs one + if (msg.needsCallbackId() && !msg.hasCallbackId()) { + msg.callbackId = this.getNextCallbackId(); + } + const machine = createSerialAPICommandMachine( msg, + msg.serialize(this.getCCEncodingContext()), { sendData: (data) => this.writeSerial(data), sendDataAbort: () => this.abortSendData(), @@ -5837,7 +5956,7 @@ ${handlers.length} left`, let node: ZWaveNode | undefined; if (isNodeQuery(msg) || isCommandClassContainer(msg)) { - node = this.getNodeUnsafe(msg); + node = this.tryGetNode(msg); } if (options.priority == undefined) { @@ -5897,6 +6016,7 @@ ${handlers.length} left`, // Create the transaction const { generator, resultPromise } = createMessageGenerator( this, + this.getCCEncodingContext(), msg, (msg, _result) => { this.handleSerialAPICommandResult(msg, options, _result); @@ -6029,12 +6149,13 @@ ${handlers.length} left`, this.controller.isFunctionSupported(FunctionType.SendDataBridge) ) { // Prioritize Bridge commands when they are supported - msg = new SendDataBridgeRequest(this, { + msg = new SendDataBridgeRequest({ + sourceNodeId: this.ownNodeId, command, maxSendAttempts: this._options.attempts.sendData, }); } else { - msg = new SendDataRequest(this, { + msg = new SendDataRequest({ command, maxSendAttempts: this._options.attempts.sendData, }); @@ -6046,12 +6167,13 @@ ${handlers.length} left`, ) ) { // Prioritize Bridge commands when they are supported - msg = new SendDataMulticastBridgeRequest(this, { + msg = new SendDataMulticastBridgeRequest({ + sourceNodeId: this.ownNodeId, command, maxSendAttempts: this._options.attempts.sendData, }); } else { - msg = new SendDataMulticastRequest(this, { + msg = new SendDataMulticastRequest({ command, maxSendAttempts: this._options.attempts.sendData, }); @@ -6090,7 +6212,7 @@ ${handlers.length} left`, * @param options (optional) Options regarding the message transmission */ private async sendCommandInternal< - TResponse extends ICommandClass = ICommandClass, + TResponse extends CCId = CCId, >( command: CommandClass, options: Omit< @@ -6139,9 +6261,10 @@ ${handlers.length} left`, }, ): Promise { // Create the encapsulating CC so we have a session ID + const sessionId = this.getNextSupervisionSessionId(command.nodeId); command = SupervisionCC.encapsulate( - this, command, + sessionId, options.requestStatusUpdates, ); @@ -6172,7 +6295,7 @@ ${handlers.length} left`, * @param options (optional) Options regarding the message transmission */ public async sendCommand< - TResponse extends ICommandClass | undefined = undefined, + TResponse extends CCId | undefined = undefined, >( command: CommandClass, options?: SendCommandOptions, @@ -6181,8 +6304,15 @@ ${handlers.length} left`, command.encapsulationFlags = options.encapsulationFlags; } - // For S2 multicast, the Security encapsulation flag does not get set automatically by the CC constructor - if (options?.s2MulticastGroupId != undefined) { + // Use security encapsulation for CCs that are only supported securely, and multicast commands + if ( + this.isCCSecure( + command.ccId, + command.nodeId as number, + command.endpointIndex, + ) + || options?.s2MulticastGroupId != undefined + ) { command.toggleEncapsulationFlag(EncapsulationFlags.Security, true); } @@ -6250,8 +6380,10 @@ ${handlers.length} left`, private async abortSendData(): Promise { try { - const abort = new SendDataAbort(this); - await this.writeSerial(abort.serialize()); + const abort = new SendDataAbort(); + await this.writeSerial( + abort.serialize(this.getCCEncodingContext()), + ); this.driverLog.logMessage(abort, { direction: "outbound", }); @@ -6369,12 +6501,12 @@ ${handlers.length} left`, * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected * @param predicate A predicate function to test all incoming command classes */ - public waitForCommand( - predicate: (cc: ICommandClass) => boolean, + public waitForCommand( + predicate: (cc: CCId) => boolean, timeout: number, ): Promise { return new Promise((resolve, reject) => { - const promise = createDeferredPromise(); + const promise = createDeferredPromise(); const entry: AwaitedCommandEntry = { predicate, handler: (cc) => promise.resolve(cc), @@ -6408,8 +6540,8 @@ ${handlers.length} left`, * Calls the given handler function every time a CommandClass is received that matches the given predicate. * @param predicate A predicate function to test all incoming command classes */ - public registerCommandHandler( - predicate: (cc: ICommandClass) => boolean, + public registerCommandHandler( + predicate: (cc: CCId) => boolean, handler: (cc: T) => void, ): { unregister: () => void; @@ -6830,7 +6962,9 @@ ${handlers.length} left`, * @internal * Marks a node for a later sleep command. Every call refreshes the period until the node actually goes to sleep */ - public debounceSendNodeToSleep(node: ZWaveNode): void { + public debounceSendNodeToSleep( + node: ZWaveNodeBase & SchedulePoll & NodeValues & NodeWakeup, + ): void { // TODO: This should be a single command to the send thread // Delete old timers if any exist if (this.sendNodeToSleepTimers.has(node.id)) { @@ -6838,7 +6972,9 @@ ${handlers.length} left`, } // Sends a node to sleep if it has no more messages. - const sendNodeToSleep = (node: ZWaveNode): void => { + const sendNodeToSleep = ( + node: ZWaveNodeBase & SchedulePoll & NodeWakeup, + ): void => { this.sendNodeToSleepTimers.delete(node.id); if (!this.hasPendingMessages(node)) { void node.sendNoMoreInformation().catch(() => { @@ -6881,7 +7017,10 @@ ${handlers.length} left`, msg = commandOrMsg; } else { const SendDataConstructor = this.getSendDataSinglecastConstructor(); - msg = new SendDataConstructor(this, { command: commandOrMsg }); + msg = new SendDataConstructor({ + sourceNodeId: this.ownNodeId, + command: commandOrMsg, + }); } if (!ignoreEncapsulation) { msg.command = this.encapsulateCommands( @@ -6935,12 +7074,13 @@ ${handlers.length} left`, } public exceedsMaxPayloadLength(msg: SendDataMessage): boolean { - return msg.serializeCC().length > this.getMaxPayloadLength(msg); + return msg.serializeCC(this.getCCEncodingContext()).length + > this.getMaxPayloadLength(msg); } /** Determines time in milliseconds to wait for a report from a node */ public getReportTimeout(msg: Message): number { - const node = this.getNodeUnsafe(msg); + const node = this.tryGetNode(msg); return ( // If there's a message-specific timeout, use that @@ -7352,7 +7492,7 @@ ${handlers.length} left`, const result = await this.sendMessage< Message & SuccessIndicator >( - new SendTestFrameRequest(this, { + new SendTestFrameRequest({ testNodeId: nodeId, powerlevel, }), diff --git a/packages/zwave-js/src/lib/driver/MessageGenerators.ts b/packages/zwave-js/src/lib/driver/MessageGenerators.ts index cff635c21168..aafa8eceab71 100644 --- a/packages/zwave-js/src/lib/driver/MessageGenerators.ts +++ b/packages/zwave-js/src/lib/driver/MessageGenerators.ts @@ -42,6 +42,7 @@ import { ZWaveErrorCodes, mergeSupervisionResults, } from "@zwave-js/core"; +import { type CCEncodingContext } from "@zwave-js/host"; import type { Message } from "@zwave-js/serial"; import { getErrorMessage } from "@zwave-js/shared"; import { wait } from "alcalzone-shared/async"; @@ -59,6 +60,7 @@ import type { MessageGenerator } from "./Transaction"; export type MessageGeneratorImplementation = ( /** A reference to the driver */ driver: Driver, + ctx: CCEncodingContext, /** The "primary" message */ message: Message, /** @@ -128,6 +130,7 @@ function getNodeUpdateTimeout( export const simpleMessageGenerator: MessageGeneratorImplementation = async function*( driver, + ctx, msg, onMessageSent, additionalCommandTimeoutMs = 0, @@ -207,7 +210,13 @@ export const simpleMessageGenerator: MessageGeneratorImplementation = /** A generator for singlecast SendData messages that automatically uses Transport Service when necessary */ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent, additionalCommandTimeoutMs) { + async function*( + driver, + ctx, + msg, + onMessageSent, + additionalCommandTimeoutMs, + ) { // Make sure we can send this message if (!isSendData(msg)) { throw new ZWaveError( @@ -221,7 +230,7 @@ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = ); } - const node = msg.getNodeUnsafe(driver); + const node = msg.tryGetNode(driver); const mayUseTransportService = node?.supportsCC(CommandClasses["Transport Service"]) && node.getCCVersion(CommandClasses["Transport Service"]) >= 2; @@ -230,6 +239,7 @@ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = // Transport Service isn't needed for this message return yield* simpleMessageGenerator( driver, + ctx, msg, onMessageSent, additionalCommandTimeoutMs, @@ -237,7 +247,7 @@ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = } // Send the command split into multiple segments - const payload = msg.serializeCC(); + const payload = msg.serializeCC(ctx); const numSegments = Math.ceil(payload.length / MAX_SEGMENT_SIZE); const segmentDelay = numSegments > RELAXED_TIMING_THRESHOLD ? TransportServiceTimeouts.relaxedTimingDelayR2 @@ -330,14 +340,14 @@ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = ); let cc: TransportServiceCC; if (segment === 0) { - cc = new TransportServiceCCFirstSegment(driver, { + cc = new TransportServiceCCFirstSegment({ nodeId, sessionId, datagramSize: payload.length, partialDatagram: chunk, }); } else { - cc = new TransportServiceCCSubsequentSegment(driver, { + cc = new TransportServiceCCSubsequentSegment({ nodeId, sessionId, datagramSize: payload.length, @@ -353,6 +363,7 @@ export const maybeTransportServiceGenerator: MessageGeneratorImplementation = }); result = yield* simpleMessageGenerator( driver, + ctx, tmsg, onMessageSent, ); @@ -465,6 +476,7 @@ async function* sendCommandGenerator< TResponse extends CommandClass = CommandClass, >( driver: Driver, + ctx: CCEncodingContext, command: CommandClass, onMessageSent: (msg: Message, result: Message | undefined) => void, options?: SendCommandOptions, @@ -473,6 +485,7 @@ async function* sendCommandGenerator< const resp = yield* maybeTransportServiceGenerator( driver, + ctx, msg, onMessageSent, ); @@ -484,7 +497,7 @@ async function* sendCommandGenerator< /** A message generator for security encapsulated messages (S0) */ export const secureMessageGeneratorS0: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { + async function*(driver, ctx, msg, onMessageSent) { if (!isSendData(msg)) { throw new ZWaveError( "Cannot use the S0 message generator for a command that's not a SendData message!", @@ -511,7 +524,7 @@ export const secureMessageGeneratorS0: MessageGeneratorImplementation = let nonce: Buffer | undefined = secMan.getFreeNonce(nodeId); if (!nonce) { // No free nonce, request a new one - const cc = new SecurityCCNonceGet(driver, { + const cc = new SecurityCCNonceGet({ nodeId: nodeId, endpoint: msg.command.endpointIndex, }); @@ -519,6 +532,7 @@ export const secureMessageGeneratorS0: MessageGeneratorImplementation = SecurityCCNonceReport >( driver, + ctx, cc, (msg, result) => { additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); @@ -542,6 +556,7 @@ export const secureMessageGeneratorS0: MessageGeneratorImplementation = // Now send the actual secure command return yield* simpleMessageGenerator( driver, + ctx, msg, onMessageSent, additionalTimeoutMs, @@ -550,7 +565,7 @@ export const secureMessageGeneratorS0: MessageGeneratorImplementation = /** A message generator for security encapsulated messages (S2) */ export const secureMessageGeneratorS2: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { + async function*(driver, ctx, msg, onMessageSent) { if (!isSendData(msg)) { throw new ZWaveError( "Cannot use the S2 message generator for a command that's not a SendData message!", @@ -588,14 +603,17 @@ export const secureMessageGeneratorS2: MessageGeneratorImplementation = // Request a new nonce // No free nonce, request a new one - const cc = new Security2CCNonceGet(driver, { + const cc = new Security2CCNonceGet({ nodeId: nodeId, + ownNodeId: driver.ownNodeId, endpoint: msg.command.endpointIndex, + securityManagers: driver, }); const nonceResp = yield* sendCommandGenerator< Security2CCNonceReport >( driver, + ctx, cc, (msg, result) => { additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); @@ -619,6 +637,7 @@ export const secureMessageGeneratorS2: MessageGeneratorImplementation = // Now send the actual secure command let response = yield* maybeTransportServiceGenerator( driver, + ctx, msg, onMessageSent, additionalTimeoutMs, @@ -688,6 +707,7 @@ export const secureMessageGeneratorS2: MessageGeneratorImplementation = msg.prepareRetransmission(); response = yield* maybeTransportServiceGenerator( driver, + ctx, msg, onMessageSent, additionalTimeoutMs, @@ -716,7 +736,7 @@ export const secureMessageGeneratorS2: MessageGeneratorImplementation = /** A message generator for security encapsulated messages (S2 Multicast) */ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { + async function*(driver, ctx, msg, onMessageSent) { if (!isSendData(msg)) { throw new ZWaveError( "Cannot use the S2 multicast message generator for a command that's not a SendData message!", @@ -754,6 +774,7 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = // Send the multicast command. We remember the transmit report and treat it as the result of the multicast command const response = yield* simpleMessageGenerator( driver, + ctx, msg, onMessageSent, ); @@ -798,6 +819,7 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = try { const scResponse = yield* secureMessageGeneratorS2( driver, + ctx, scMsg, onMessageSent, ); @@ -812,8 +834,10 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = const innerMPANState = secMan.getInnerMPANState(groupId); // This should always be defined, but better not throw unnecessarily here if (innerMPANState) { - const cc = new Security2CCMessageEncapsulation(driver, { + const cc = new Security2CCMessageEncapsulation({ nodeId, + ownNodeId: driver.ownNodeId, + securityManagers: driver, extensions: [ new MPANExtension({ groupId, @@ -823,17 +847,23 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = }); // Send it the MPAN - yield* sendCommandGenerator(driver, cc, onMessageSent, { - // Seems we need these options or some nodes won't accept the nonce - transmitOptions: TransmitOptions.ACK - | TransmitOptions.AutoRoute, - // Only try sending a nonce once - maxSendAttempts: 1, - // Nonce requests must be handled immediately - priority: MessagePriority.Immediate, - // We don't want failures causing us to treat the node as asleep or dead - changeNodeStatusOnMissingACK: false, - }); + yield* sendCommandGenerator( + driver, + ctx, + cc, + onMessageSent, + { + // Seems we need these options or some nodes won't accept the nonce + transmitOptions: TransmitOptions.ACK + | TransmitOptions.AutoRoute, + // Only try sending a nonce once + maxSendAttempts: 1, + // Nonce requests must be handled immediately + priority: MessagePriority.Immediate, + // We don't want failures causing us to treat the node as asleep or dead + changeNodeStatusOnMissingACK: false, + }, + ); } } @@ -866,16 +896,16 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = if (finalSupervisionResult) { // We can return return information about the success of this multicast - so we should // TODO: Not sure if we need to "wrap" the response for something. For now, try faking it - const cc = new SupervisionCCReport(driver, { + const cc = new SupervisionCCReport({ nodeId: NODE_ID_BROADCAST, sessionId: 0, // fake moreUpdatesFollow: false, // fake ...(finalSupervisionResult as any), }); - const ret = new (driver.getSendDataSinglecastConstructor())( - driver, - { command: cc }, - ); + const ret = new (driver.getSendDataSinglecastConstructor())({ + sourceNodeId: driver.ownNodeId, + command: cc, + }); return ret; } else { return response; @@ -884,6 +914,7 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = export function createMessageGenerator( driver: Driver, + ctx: CCEncodingContext, msg: Message, onMessageSent: (msg: Message, result: Message | undefined) => void, ): { @@ -923,7 +954,7 @@ export function createMessageGenerator( // Step through the generator so we can easily cancel it and don't // accidentally forget to unset this.current at the end - const gen = implementation(driver, msg, onMessageSent); + const gen = implementation(driver, ctx, msg, onMessageSent); let sendResult: Message | undefined; let result: Message | undefined; while (true) { diff --git a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.test.ts b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.test.ts index 73a1ca3d9c77..bbb96eb3eabb 100644 --- a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.test.ts +++ b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.test.ts @@ -440,10 +440,12 @@ testPlans.forEach((plan) => { } // And create a test machine with it - const msg = JSON.parse(match.groups.json); + const msgSelector = JSON.parse(match.groups.json); + // @ts-ignore + const message: Message = messages[msgSelector.resp][msgSelector.cb]; const machine = createSerialAPICommandMachine( - // @ts-ignore - messages[msg.resp][msg.cb], + message, + message.serialize({} as any), implementations, machineParams, ); diff --git a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts index 04ea7db84021..65f788e955ed 100644 --- a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts +++ b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts @@ -141,6 +141,7 @@ export type SerialAPICommandMachineParams = { export function getSerialAPICommandMachineConfig( message: Message, + messageData: Buffer, { timestamp, logOutgoingMessage, @@ -161,7 +162,7 @@ export function getSerialAPICommandMachineConfig( initial: "sending", context: { msg: message, - data: message.serialize(), + data: messageData, attempts: 0, maxAttempts: attemptsConfig.controller, }, @@ -449,12 +450,14 @@ export function getSerialAPICommandMachineOptions( export function createSerialAPICommandMachine( message: Message, + messageData: Buffer, implementations: SerialAPICommandServiceImplementations, params: SerialAPICommandMachineParams, ): SerialAPICommandMachine { return createMachine( getSerialAPICommandMachineConfig( message, + messageData, implementations, params.attempts, ), diff --git a/packages/zwave-js/src/lib/driver/Transaction.test.ts b/packages/zwave-js/src/lib/driver/Transaction.test.ts index ef0d707d25d7..0c8b418767c1 100644 --- a/packages/zwave-js/src/lib/driver/Transaction.test.ts +++ b/packages/zwave-js/src/lib/driver/Transaction.test.ts @@ -49,8 +49,8 @@ test("should compare priority, then the timestamp", (t) => { controller: { nodes: new Map(), }, - get nodes() { - return driverMock.controller.nodes; + getNode(nodeId: number) { + return driverMock.controller.nodes.get(nodeId); }, getSafeCCVersion() {}, getSupportedCCVersion() {}, @@ -147,8 +147,8 @@ test("NodeQuery comparisons should prioritize listening nodes", (t) => { ], ]), }, - get nodes() { - return driverMock.controller.nodes; + getNode(nodeId: number) { + return driverMock.controller.nodes.get(nodeId); }, getSafeCCVersion() {}, getSupportedCCVersion() {}, @@ -164,12 +164,12 @@ test("NodeQuery comparisons should prioritize listening nodes", (t) => { ) { const driver = driverMock as any as Driver; const msg = nodeId != undefined - ? new SendDataRequest(driver, { - command: new NoOperationCC(driver, { + ? new SendDataRequest({ + command: new NoOperationCC({ nodeId, }), }) - : new GetControllerVersionRequest(driver); + : new GetControllerVersionRequest(); const ret = createDummyTransaction(driverMock, { priority, message: msg, @@ -260,8 +260,8 @@ test("Messages in the wakeup queue should be preferred over lesser priorities on ], ]), }, - get nodes() { - return driverMock.controller.nodes; + getNode(nodeId: number) { + return driverMock.controller.nodes.get(nodeId); }, getSafeCCVersion() {}, getSupportedCCVersion() {}, @@ -273,8 +273,8 @@ test("Messages in the wakeup queue should be preferred over lesser priorities on function createTransaction(nodeId: number, priority: MessagePriority) { const driver = driverMock as any as Driver; - const msg = new SendDataRequest(driver, { - command: new NoOperationCC(driver, { nodeId }), + const msg = new SendDataRequest({ + command: new NoOperationCC({ nodeId }), }); const ret = createDummyTransaction(driverMock, { priority, @@ -356,8 +356,8 @@ test("Controller message should be preferred over messages for sleeping nodes", ], ]), }, - get nodes() { - return driverMock.controller.nodes; + getNode(nodeId: number) { + return driverMock.controller.nodes.get(nodeId); }, getSafeCCVersion() {}, getSupportedCCVersion() {}, @@ -380,14 +380,14 @@ test("Controller message should be preferred over messages for sleeping nodes", return ret; } - const msgForSleepingNode = new SendDataRequest(driverMock, { - command: new NoOperationCC(driverMock, { nodeId: 2 }), + const msgForSleepingNode = new SendDataRequest({ + command: new NoOperationCC({ nodeId: 2 }), }); const tSleepingNode = createTransaction( msgForSleepingNode, MessagePriority.WakeUp, ); - const msgForController = new RemoveFailedNodeRequest(driverMock, { + const msgForController = new RemoveFailedNodeRequest({ failedNodeId: 3, }); const tController = createTransaction(msgForController); @@ -401,8 +401,8 @@ test("should capture a stack trace where it was created", (t) => { controller: { nodes: new Map(), }, - get nodes() { - return driverMock.controller.nodes; + getNode(nodeId: number) { + return driverMock.controller.nodes.get(nodeId); }, getSafeCCVersion() {}, getSupportedCCVersion() {}, diff --git a/packages/zwave-js/src/lib/driver/Transaction.ts b/packages/zwave-js/src/lib/driver/Transaction.ts index 46c7cd8bcceb..a2064e7ecc2d 100644 --- a/packages/zwave-js/src/lib/driver/Transaction.ts +++ b/packages/zwave-js/src/lib/driver/Transaction.ts @@ -194,8 +194,8 @@ export class Transaction implements Comparable { _this: Transaction, _other: Transaction, ): CompareResult | undefined => { - const thisNode = _this.message.getNodeUnsafe(this.driver); - const otherNode = _other.message.getNodeUnsafe(this.driver); + const thisNode = _this.message.tryGetNode(this.driver); + const otherNode = _other.message.tryGetNode(this.driver); // We don't require existence of the node object // If any transaction is not for a node, it targets the controller @@ -222,8 +222,8 @@ export class Transaction implements Comparable { _this: Transaction, _other: Transaction, ): CompareResult | undefined => { - const thisNode = _this.message.getNodeUnsafe(this.driver); - const otherNode = _other.message.getNodeUnsafe(this.driver); + const thisNode = _this.message.tryGetNode(this.driver); + const otherNode = _other.message.tryGetNode(this.driver); if (thisNode && otherNode) { // Both nodes exist const thisListening = thisNode.isListening diff --git a/packages/zwave-js/src/lib/log/Driver.test.ts b/packages/zwave-js/src/lib/log/Driver.test.ts index 589d00921085..849c597323b1 100644 --- a/packages/zwave-js/src/lib/log/Driver.test.ts +++ b/packages/zwave-js/src/lib/log/Driver.test.ts @@ -88,7 +88,7 @@ function createMessage( driver: Driver, options: Partial, ) { - return new Message(driver, { + return new Message({ type: options.type || MessageType.Request, functionType: options.functionType || (0x00 as any), }); diff --git a/packages/zwave-js/src/lib/log/Driver.ts b/packages/zwave-js/src/lib/log/Driver.ts index 2be109c6dfe6..332e381f60ad 100644 --- a/packages/zwave-js/src/lib/log/Driver.ts +++ b/packages/zwave-js/src/lib/log/Driver.ts @@ -197,7 +197,7 @@ export class DriverLogger extends ZWaveLoggerBase { if (queue.length > 0) { for (const trns of queue.transactions) { // TODO: This formatting should be shared with the other logging methods - const node = trns.message.getNodeUnsafe(this.driver); + const node = trns.message.tryGetNode(this.driver); const prefix = trns.message.type === MessageType.Request ? "[REQ]" : "[RES]"; diff --git a/packages/zwave-js/src/lib/node/Endpoint.ts b/packages/zwave-js/src/lib/node/Endpoint.ts index 7fc66014b838..3c11901c3b47 100644 --- a/packages/zwave-js/src/lib/node/Endpoint.ts +++ b/packages/zwave-js/src/lib/node/Endpoint.ts @@ -10,7 +10,15 @@ import { normalizeCCNameOrId, } from "@zwave-js/cc"; import { ZWavePlusCCValues } from "@zwave-js/cc/ZWavePlusCC"; -import type { IZWaveEndpoint, MaybeNotKnown } from "@zwave-js/core"; +import type { + ControlsCC, + EndpointId, + GetCCs, + IsCCSecure, + MaybeNotKnown, + ModifyCCs, + SupportsCC, +} from "@zwave-js/core"; import { BasicDeviceClass, CacheBackedMap, @@ -36,7 +44,9 @@ import type { ZWaveNode } from "./Node"; * * Each endpoint may have different capabilities (device class/supported CCs) */ -export class Endpoint implements IZWaveEndpoint { +export class Endpoint + implements EndpointId, SupportsCC, ControlsCC, IsCCSecure, ModifyCCs, GetCCs +{ public constructor( /** The id of the node this endpoint belongs to */ public readonly nodeId: number, @@ -102,7 +112,7 @@ export class Endpoint implements IZWaveEndpoint { /** Can be used to distinguish multiple endpoints of a node */ public get endpointLabel(): string | undefined { - return this.getNodeUnsafe()?.deviceConfig?.endpoints?.get(this.index) + return this.tryGetNode()?.deviceConfig?.endpoints?.get(this.index) ?.label; } @@ -196,7 +206,7 @@ export class Endpoint implements IZWaveEndpoint { public wasCCRemovedViaConfig(cc: CommandClasses): boolean { if (this.supportsCC(cc)) return false; - const compatConfig = this.getNodeUnsafe()?.deviceConfig?.compat; + const compatConfig = this.tryGetNode()?.deviceConfig?.compat; if (!compatConfig) return false; const removedEndpoints = compatConfig.removeCCs?.get(cc); @@ -230,7 +240,7 @@ export class Endpoint implements IZWaveEndpoint { // an unnecessary Version CC interview for each endpoint or an incorrect V1 for endpoints if (ret === 0 && this.index > 0) { - return this.getNodeUnsafe()!.getCCVersion(cc); + return this.tryGetNode()!.getCCVersion(cc); } return ret; } @@ -251,7 +261,7 @@ export class Endpoint implements IZWaveEndpoint { ZWaveErrorCodes.CC_NotSupported, ); } - return CommandClass.createInstanceUnchecked(this.driver, this, cc); + return CommandClass.createInstanceUnchecked(this, cc); } /** @@ -263,7 +273,7 @@ export class Endpoint implements IZWaveEndpoint { ): T | undefined { const ccId = typeof cc === "number" ? cc : getCommandClassStatic(cc); if (this.supportsCC(ccId) || this.controlsCC(ccId)) { - return CommandClass.createInstanceUnchecked(this.driver, this, cc); + return CommandClass.createInstanceUnchecked(this, cc); } } @@ -434,20 +444,20 @@ export class Endpoint implements IZWaveEndpoint { /** * Returns the node this endpoint belongs to (or undefined if the node doesn't exist) */ - public getNodeUnsafe(): ZWaveNode | undefined { + public tryGetNode(): ZWaveNode | undefined { return this.driver.controller.nodes.get(this.nodeId); } /** Z-Wave+ Icon (for management) */ public get installerIcon(): MaybeNotKnown { - return this.getNodeUnsafe()?.getValue( + return this.tryGetNode()?.getValue( ZWavePlusCCValues.installerIcon.endpoint(this.index), ); } /** Z-Wave+ Icon (for end users) */ public get userIcon(): MaybeNotKnown { - return this.getNodeUnsafe()?.getValue( + return this.tryGetNode()?.getValue( ZWavePlusCCValues.userIcon.endpoint(this.index), ); } diff --git a/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts b/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts index 402720e709b4..3639dd80f3f0 100644 --- a/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts +++ b/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts @@ -46,7 +46,7 @@ const respondToRequestNodeInfo: MockNodeBehavior = { receivedCC instanceof ZWaveProtocolCCRequestNodeInformationFrame ) { - const cc = new ZWaveProtocolCCNodeInformationFrame(self.host, { + const cc = new ZWaveProtocolCCNodeInformationFrame({ nodeId: self.id, ...self.capabilities, supportedCCs: [...self.implementedCCs] @@ -85,7 +85,7 @@ const respondToVersionCCCommandClassGet: MockNodeBehavior = { version = 1; } - const cc = new VersionCCCommandClassReport(self.host, { + const cc = new VersionCCCommandClassReport({ nodeId: self.id, endpoint: "index" in endpoint ? endpoint.index : undefined, requestedCC: receivedCC.requestedCC, @@ -99,8 +99,8 @@ const respondToVersionCCCommandClassGet: MockNodeBehavior = { const respondToZWavePlusCCGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof ZWavePlusCCGet) { - const cc = new ZWavePlusCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ZWavePlusCCReport({ + nodeId: controller.ownNodeId, zwavePlusVersion: 2, nodeType: ZWavePlusNodeType.Node, roleType: self.capabilities.isListening @@ -123,8 +123,8 @@ const respondToS0ZWavePlusCCGet: MockNodeBehavior = { receivedCC instanceof SecurityCCCommandEncapsulation && receivedCC.encapsulated instanceof ZWavePlusCCGet ) { - let cc: CommandClass = new ZWavePlusCCReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc: CommandClass = new ZWavePlusCCReport({ + nodeId: controller.ownNodeId, zwavePlusVersion: 2, nodeType: ZWavePlusNodeType.Node, roleType: self.capabilities.isListening @@ -135,7 +135,11 @@ const respondToS0ZWavePlusCCGet: MockNodeBehavior = { installerIcon: 0x0000, userIcon: 0x0000, }); - cc = SecurityCC.encapsulate(self.host, cc); + cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + cc, + ); return { action: "sendCC", cc, ackRequested: true }; } }, @@ -147,8 +151,8 @@ const respondToS2ZWavePlusCCGet: MockNodeBehavior = { receivedCC instanceof Security2CCMessageEncapsulation && receivedCC.encapsulated instanceof ZWavePlusCCGet ) { - let cc: CommandClass = new ZWavePlusCCReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc: CommandClass = new ZWavePlusCCReport({ + nodeId: controller.ownNodeId, zwavePlusVersion: 2, nodeType: ZWavePlusNodeType.Node, roleType: self.capabilities.isListening @@ -159,7 +163,11 @@ const respondToS2ZWavePlusCCGet: MockNodeBehavior = { installerIcon: 0x0000, userIcon: 0x0000, }); - cc = Security2CC.encapsulate(self.host, cc); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, + ); return { action: "sendCC", cc }; } }, diff --git a/packages/zwave-js/src/lib/node/MultiCCAPIWrapper.ts b/packages/zwave-js/src/lib/node/MultiCCAPIWrapper.ts index 9bdeda6d99a1..c6993ccc2bc5 100644 --- a/packages/zwave-js/src/lib/node/MultiCCAPIWrapper.ts +++ b/packages/zwave-js/src/lib/node/MultiCCAPIWrapper.ts @@ -57,7 +57,7 @@ export function createMultiCCAPIWrapper(apiInstances: T[]): T { // This wrapper is by definition for multiple nodes, so we cannot return one const getNode = () => undefined; - const getNodeUnsafe = () => undefined; + const tryGetNode = () => undefined; return new Proxy({} as T, { get(target, prop) { @@ -81,8 +81,8 @@ export function createMultiCCAPIWrapper(apiInstances: T[]): T { return isSupported; case "getNode": return getNode; - case "getNodeUnsafe": - return getNodeUnsafe; + case "tryGetNode": + return tryGetNode; case "isSetValueOptimistic": return isSetValueOptimistic; case "supportsCommand": diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index c623b2dda576..b23bf7b15cec 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -1,7 +1,6 @@ import { AssociationGroupInfoProfile, type CCAPI, - type CCValueOptions, CentralSceneKeys, ClockCommand, CommandClass, @@ -10,13 +9,6 @@ import { DoorLockMode, EntryControlDataTypes, type FirmwareUpdateCapabilities, - type FirmwareUpdateInitResult, - type FirmwareUpdateMetaData, - type FirmwareUpdateOptions, - type FirmwareUpdateProgress, - FirmwareUpdateRequestStatus, - type FirmwareUpdateResult, - FirmwareUpdateStatus, InclusionControllerCCInitiate, InclusionControllerStep, IndicatorCCDescriptionGet, @@ -44,9 +36,8 @@ import { type ValueIDProperties, ZWavePlusNodeType, ZWavePlusRoleType, - defaultCCValueOptions, entryControlEventTypeLabels, - getCCValues, + getEffectiveCCVersion, getImplementedVersion, isCommandClassContainer, utils as ccUtils, @@ -83,11 +74,8 @@ import { ClockCCReport } from "@zwave-js/cc/ClockCC"; import { DoorLockCCValues } from "@zwave-js/cc/DoorLockCC"; import { EntryControlCCNotification } from "@zwave-js/cc/EntryControlCC"; import { - FirmwareUpdateMetaDataCC, FirmwareUpdateMetaDataCCGet, FirmwareUpdateMetaDataCCMetaDataGet, - FirmwareUpdateMetaDataCCReport, - FirmwareUpdateMetaDataCCStatusReport, FirmwareUpdateMetaDataCCValues, } from "@zwave-js/cc/FirmwareUpdateMetaDataCC"; import { HailCC } from "@zwave-js/cc/HailCC"; @@ -96,7 +84,6 @@ import { ManufacturerSpecificCCGet, ManufacturerSpecificCCValues, } from "@zwave-js/cc/ManufacturerSpecificCC"; -import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; import { MultilevelSwitchCC, MultilevelSwitchCCSet, @@ -155,18 +142,11 @@ import { import { type DeviceConfig, embeddedDevicesDir } from "@zwave-js/config"; import { BasicDeviceClass, - CRC16_CCITT, - CacheBackedMap, CommandClasses, - type DataRate, Duration, EncapsulationFlags, - type FLiRS, - type Firmware, - type IZWaveNode, type MaybeNotKnown, MessagePriority, - type MetadataUpdatedArgs, NOT_KNOWN, NodeType, type NodeUpdatePayload, @@ -174,10 +154,10 @@ import { type NotificationState, ProtocolVersion, Protocols, + type QuerySecurityClasses, type RSSI, RssiError, SecurityClass, - type SecurityClassOwner, type SendCommandOptions, type SetValueOptions, type SinglecastCC, @@ -185,12 +165,10 @@ import { type TXReport, type TranslatedValueID, TransmitOptions, - ValueDB, + type ValueDB, type ValueID, - ValueMetadata, + type ValueMetadata, type ValueMetadataNumeric, - type ValueRemovedArgs, - type ValueUpdatedArgs, ZWaveError, ZWaveErrorCodes, ZWaveLibraryTypes, @@ -203,7 +181,6 @@ import { getDSTInfo, getNotification, getNotificationValue, - isLongRangeNodeId, isRssiError, isSupervisionResult, isTransmissionError, @@ -218,15 +195,12 @@ import { serializeCacheValue, supervisedCommandFailed, supervisedCommandSucceeded, - timespan, topologicalSort, valueIdToString, } from "@zwave-js/core"; -import type { NodeSchedulePollOptions } from "@zwave-js/host"; import { FunctionType, type Message } from "@zwave-js/serial"; import { Mixin, - ObjectKeyMap, type TypedEventEmitter, cloneDeep, discreteLinearSearch, @@ -236,9 +210,7 @@ import { noop, pick, stringify, - throttle, } from "@zwave-js/shared"; -import { distinct } from "alcalzone-shared/arrays"; import { wait } from "alcalzone-shared/async"; import { type DeferredPromise, @@ -247,18 +219,14 @@ import { import { roundTo } from "alcalzone-shared/math"; import { padStart } from "alcalzone-shared/strings"; import { isArray, isObject } from "alcalzone-shared/typeguards"; -import { randomBytes } from "node:crypto"; import { EventEmitter } from "node:events"; import path from "node:path"; -import { isDeepStrictEqual } from "node:util"; import semver from "semver"; import { RemoveNodeReason } from "../controller/Inclusion"; import { determineNIF } from "../controller/NodeInformationFrame"; import { type Driver, libVersion } from "../driver/Driver"; import { cacheKeys } from "../driver/NetworkCache"; -import { type Extended, interpretEx } from "../driver/StateMachineShared"; import type { StatisticsEventCallbacksWithSelf } from "../driver/Statistics"; -import { type TaskBuilder, TaskPriority } from "../driver/Task"; import type { Transaction } from "../driver/Transaction"; import { type ApplicationUpdateRequest, @@ -275,27 +243,18 @@ import { } from "../serialapi/network-mgmt/RequestNodeInfoMessages"; import { DeviceClass } from "./DeviceClass"; import { type NodeDump, type ValueDump } from "./Dump"; -import { Endpoint } from "./Endpoint"; +import { type Endpoint } from "./Endpoint"; import { formatLifelineHealthCheckSummary, formatRouteHealthCheckSummary, healthCheckTestFrameCount, } from "./HealthCheck"; -import { - type NodeReadyInterpreter, - createNodeReadyMachine, -} from "./NodeReadyMachine"; import { type NodeStatistics, NodeStatisticsHost, type RouteStatistics, routeStatisticsEquals, } from "./NodeStatistics"; -import { - type NodeStatusInterpreter, - createNodeStatusMachine, - nodeStatusMachineStateToNodeStatus, -} from "./NodeStatusMachine"; import { type DateAndTime, type LifelineHealthCheckResult, @@ -307,34 +266,19 @@ import { type RouteHealthCheckResult, type RouteHealthCheckSummary, type ZWaveNodeEventCallbacks, - type ZWaveNodeValueEventCallbacks, } from "./_Types"; import { InterviewStage, NodeStatus } from "./_Types"; +import { ZWaveNodeMixins } from "./mixins"; import * as nodeUtils from "./utils"; -interface ScheduledPoll { - timeout: NodeJS.Timeout; - expectedValue?: unknown; -} - -interface AbortFirmwareUpdateContext { - abort: boolean; - tooLateToAbort: boolean; - abortPromise: DeferredPromise; -} - -type PartialFirmwareUpdateResult = - & Pick - & { success: boolean }; - const MAX_ASSOCIATIONS = 1; -export interface ZWaveNode extends - TypedEventEmitter< - & ZWaveNodeEventCallbacks - & StatisticsEventCallbacksWithSelf - >, - NodeStatisticsHost +type AllNodeEvents = + & ZWaveNodeEventCallbacks + & StatisticsEventCallbacksWithSelf; + +export interface ZWaveNode + extends TypedEventEmitter, NodeStatisticsHost {} /** @@ -342,93 +286,27 @@ export interface ZWaveNode extends * of its root endpoint (index 0) */ @Mixin([EventEmitter, NodeStatisticsHost]) -export class ZWaveNode extends Endpoint - implements SecurityClassOwner, IZWaveNode -{ +export class ZWaveNode extends ZWaveNodeMixins implements QuerySecurityClasses { public constructor( - public readonly id: number, + id: number, driver: Driver, deviceClass?: DeviceClass, supportedCCs: CommandClasses[] = [], controlledCCs: CommandClasses[] = [], valueDB?: ValueDB, ) { - // Define this node's intrinsic endpoint as the root device (0) - super(id, driver, 0, deviceClass, supportedCCs); - this._valueDB = valueDB - ?? new ValueDB(id, driver.valueDB!, driver.metadataDB!); - // Pass value events to our listeners - for ( - const event of [ - "value added", - "value updated", - "value removed", - "value notification", - "metadata updated", - ] as const - ) { - this._valueDB.on(event, this.translateValueEvent.bind(this, event)); - } - - // Also avoid verifying a value change for which we recently received an update - for (const event of ["value updated", "value removed"] as const) { - this._valueDB.on( - event, - (args: ValueUpdatedArgs | ValueRemovedArgs) => { - // Value updates caused by the driver should never cancel a scheduled poll - if ("source" in args && args.source === "driver") return; - - if ( - this.cancelScheduledPoll( - args, - (args as ValueUpdatedArgs).newValue, - ) - ) { - this.driver.controllerLog.logNode( - this.id, - "Scheduled poll canceled because expected value was received", - "verbose", - ); - } - }, - ); - } - - this.securityClasses = new CacheBackedMap(this.driver.networkCache, { - prefix: cacheKeys.node(this.id)._securityClassBaseKey + ".", - suffixSerializer: (value: SecurityClass) => - getEnumMemberName(SecurityClass, value), - suffixDeserializer: (key: string) => { - if ( - key in SecurityClass - && typeof (SecurityClass as any)[key] === "number" - ) { - return (SecurityClass as any)[key]; - } - }, - }); + super( + id, + driver, + // Define this node's intrinsic endpoint as the root device (0) + 0, + deviceClass, + supportedCCs, + valueDB, + ); // Add optional controlled CCs - endpoints don't have this for (const cc of controlledCCs) this.addCC(cc, { isControlled: true }); - - // Create and hook up the status machine - this.statusMachine = interpretEx(createNodeStatusMachine(this)); - this.statusMachine.onTransition((state) => { - if (state.changed) { - this.onStatusChange( - nodeStatusMachineStateToNodeStatus(state.value as any), - ); - } - }); - this.statusMachine.start(); - - this.readyMachine = interpretEx(createNodeReadyMachine()); - this.readyMachine.onTransition((state) => { - if (state.changed) { - this.onReadyChange(state.value === "ready"); - } - }); - this.readyMachine.start(); } /** @@ -453,293 +331,9 @@ export class ZWaveNode extends Endpoint this.removeAllListeners(); // Clear all scheduled polls that would interfere with the interview - for (const valueId of this.scheduledPolls.keys()) { - this.cancelScheduledPoll(valueId); - } - } - - /** - * Enhances the raw event args of the ValueDB so it can be consumed better by applications - */ - private translateValueEvent( - eventName: keyof ZWaveNodeValueEventCallbacks, - arg: T, - ): void { - // Try to retrieve the speaking CC name - const outArg = nodeUtils.translateValueID(this.driver, this, arg); - // This can happen for value updated events - if ("source" in outArg) delete outArg.source; - - const loglevel = this.driver.getLogConfig().level; - - // If this is a metadata event, make sure we return the merged metadata - if ("metadata" in outArg) { - (outArg as unknown as MetadataUpdatedArgs).metadata = this - .getValueMetadata(arg); - } - - const ccInstance = CommandClass.createInstanceUnchecked( - this.driver, - this, - arg.commandClass, - ); - const isInternalValue = !!ccInstance?.isInternalValue(arg); - // Check whether this value change may be logged - const isSecretValue = !!ccInstance?.isSecretValue(arg); - - if (loglevel === "silly") { - this.driver.controllerLog.logNode(this.id, { - message: `[translateValueEvent: ${eventName}] - commandClass: ${getCCName(arg.commandClass)} - endpoint: ${arg.endpoint} - property: ${arg.property} - propertyKey: ${arg.propertyKey} - internal: ${isInternalValue} - secret: ${isSecretValue} - event source: ${(arg as any as ValueUpdatedArgs).source}`, - level: "silly", - }); - } - - if ( - !isSecretValue - && (arg as any as ValueUpdatedArgs).source !== "driver" - ) { - // Log the value change, except for updates caused by the driver itself - // I don't like the splitting and any but its the easiest solution here - const [changeTarget, changeType] = eventName.split(" "); - const logArgument = { - ...outArg, - nodeId: this.id, - internal: isInternalValue, - }; - if (changeTarget === "value") { - this.driver.controllerLog.value( - changeType as any, - logArgument as any, - ); - } else if (changeTarget === "metadata") { - this.driver.controllerLog.metadataUpdated(logArgument); - } - } - - // Don't expose value events for internal value IDs... - if (isInternalValue) return; - - if (loglevel === "silly") { - this.driver.controllerLog.logNode(this.id, { - message: `[translateValueEvent: ${eventName}] - is root endpoint: ${!arg.endpoint} - is application CC: ${applicationCCs.includes(arg.commandClass)} - should hide root values: ${ - nodeUtils.shouldHideRootApplicationCCValues( - this.driver, - this, - ) - }`, - level: "silly", - }); - } - - // ... and root values ID that mirrors endpoint functionality - if ( - // Only root endpoint values need to be filtered - !arg.endpoint - // Only application CCs need to be filtered - && applicationCCs.includes(arg.commandClass) - // and only if the endpoints are not unnecessary and the root values mirror them - && nodeUtils.shouldHideRootApplicationCCValues(this.driver, this) - ) { - // Iterate through all possible non-root endpoints of this node and - // check if there is a value ID that mirrors root endpoint functionality - for (const endpoint of this.getEndpointIndizes()) { - const possiblyMirroredValueID: ValueID = { - // same CC, property and key - ...pick(arg, ["commandClass", "property", "propertyKey"]), - // but different endpoint - endpoint, - }; - if (this.valueDB.hasValue(possiblyMirroredValueID)) { - if (loglevel === "silly") { - this.driver.controllerLog.logNode(this.id, { - message: - `[translateValueEvent: ${eventName}] found mirrored value ID on different endpoint, ignoring event: - commandClass: ${getCCName(possiblyMirroredValueID.commandClass)} - endpoint: ${possiblyMirroredValueID.endpoint} - property: ${possiblyMirroredValueID.property} - propertyKey: ${possiblyMirroredValueID.propertyKey}`, - level: "silly", - }); - } - - return; - } - } - } - // And pass the translated event to our listeners - this.emit(eventName, this, outArg as any); - } - - private statusMachine: Extended; - private _status: NodeStatus = NodeStatus.Unknown; - - private onStatusChange(newStatus: NodeStatus) { - // Ignore duplicate events - if (newStatus === this._status) return; - - const oldStatus = this._status; - this._status = newStatus; - if (this._status === NodeStatus.Asleep) { - this.emit("sleep", this, oldStatus); - } else if (this._status === NodeStatus.Awake) { - this.emit("wake up", this, oldStatus); - } else if (this._status === NodeStatus.Dead) { - this.emit("dead", this, oldStatus); - } else if (this._status === NodeStatus.Alive) { - this.emit("alive", this, oldStatus); - } - - // To be marked ready, a node must be known to be not dead. - // This means that listening nodes must have communicated with us and - // sleeping nodes are assumed to be ready - this.readyMachine.send( - this._status !== NodeStatus.Unknown - && this._status !== NodeStatus.Dead - ? "NOT_DEAD" - : "MAYBE_DEAD", - ); - } - - /** - * Which status the node is believed to be in - */ - public get status(): NodeStatus { - return this._status; - } - - /** - * @internal - * Marks this node as dead (if applicable) - */ - public markAsDead(): void { - this.statusMachine.send("DEAD"); - } - - /** - * @internal - * Marks this node as alive (if applicable) - */ - public markAsAlive(): void { - this.statusMachine.send("ALIVE"); - } - - /** - * @internal - * Marks this node as asleep (if applicable) - */ - public markAsAsleep(): void { - this.statusMachine.send("ASLEEP"); - } - - /** - * @internal - * Marks this node as awake (if applicable) - */ - public markAsAwake(): void { - this.statusMachine.send("AWAKE"); - } - - /** Returns a promise that resolves when the node wakes up the next time or immediately if the node is already awake. */ - public waitForWakeup(): Promise { - if (!this.canSleep || !this.supportsCC(CommandClasses["Wake Up"])) { - throw new ZWaveError( - `Node ${this.id} does not support wakeup!`, - ZWaveErrorCodes.CC_NotSupported, - ); - } else if (this._status === NodeStatus.Awake) { - return Promise.resolve(); - } - - return new Promise((resolve) => { - this.once("wake up", () => resolve()); - }); - } - - // The node is only ready when the interview has been completed - // to a certain degree - - private readyMachine: Extended; - private _ready: boolean = false; - - private onReadyChange(ready: boolean) { - // Ignore duplicate events - if (ready === this._ready) return; - - this._ready = ready; - if (ready) this.emit("ready", this); - } - - /** - * Whether the node is ready to be used - */ - public get ready(): boolean { - return this._ready; - } - - /** Whether this node is always listening or not */ - public get isListening(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).isListening); - } - private set isListening(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).isListening, value); - } - - /** Indicates the wakeup interval if this node is a FLiRS node. `false` if it isn't. */ - public get isFrequentListening(): MaybeNotKnown { - return this.driver.cacheGet( - cacheKeys.node(this.id).isFrequentListening, - ); - } - private set isFrequentListening(value: MaybeNotKnown) { - this.driver.cacheSet( - cacheKeys.node(this.id).isFrequentListening, - value, - ); - } - - public get canSleep(): MaybeNotKnown { - // The controller node can never sleep (apparently it can report otherwise though) - if (this.isControllerNode) return false; - if (this.isListening == NOT_KNOWN) return NOT_KNOWN; - if (this.isFrequentListening == NOT_KNOWN) return NOT_KNOWN; - return !this.isListening && !this.isFrequentListening; - } - - /** Whether the node supports routing/forwarding messages. */ - public get isRouting(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).isRouting); - } - private set isRouting(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).isRouting, value); - } - - public get supportedDataRates(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).supportedDataRates); - } - private set supportedDataRates(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).supportedDataRates, value); - } - - public get maxDataRate(): MaybeNotKnown { - if (this.supportedDataRates) { - return Math.max(...this.supportedDataRates) as DataRate; - } + this.cancelAllScheduledPolls(); } - /** @internal */ - // This a CacheBackedMap that's assigned in the constructor - public readonly securityClasses: Map; - /** * The device specific key (DSK) of this node in binary format. * This is only set if included with Security S2. @@ -753,76 +347,6 @@ export class ZWaveNode extends Endpoint this.driver.cacheSet(cacheKey, value); } - /** Whether the node was granted at least one security class */ - public get isSecure(): MaybeNotKnown { - const securityClass = this.getHighestSecurityClass(); - if (securityClass == undefined) return NOT_KNOWN; - if (securityClass === SecurityClass.None) return false; - return true; - } - - public hasSecurityClass( - securityClass: SecurityClass, - ): MaybeNotKnown { - return this.securityClasses.get(securityClass); - } - - public setSecurityClass( - securityClass: SecurityClass, - granted: boolean, - ): void { - this.securityClasses.set(securityClass, granted); - } - - /** Returns the highest security class this node was granted or `undefined` if that information isn't known yet */ - public getHighestSecurityClass(): MaybeNotKnown { - if (this.securityClasses.size === 0) return undefined; - let missingSome = false; - for (const secClass of securityClassOrder) { - if (this.securityClasses.get(secClass) === true) return secClass; - if (!this.securityClasses.has(secClass)) { - missingSome = true; - } - } - // If we don't have the info for every security class, we don't know the highest one yet - return missingSome ? NOT_KNOWN : SecurityClass.None; - } - - /** The Z-Wave protocol version this node implements */ - public get protocolVersion(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).protocolVersion); - } - private set protocolVersion(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).protocolVersion, value); - } - - /** Whether this node is a controller (can calculate routes) or an end node (relies on route info) */ - public get nodeType(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).nodeType); - } - private set nodeType(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).nodeType, value); - } - - /** - * Whether this node supports security (S0 or S2). - * **WARNING:** Nodes often report this incorrectly - do not blindly trust it. - */ - public get supportsSecurity(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).supportsSecurity); - } - private set supportsSecurity(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).supportsSecurity, value); - } - - /** Whether this node can issue wakeup beams to FLiRS nodes */ - public get supportsBeaming(): MaybeNotKnown { - return this.driver.cacheGet(cacheKeys.node(this.id).supportsBeaming); - } - private set supportsBeaming(value: MaybeNotKnown) { - this.driver.cacheSet(cacheKeys.node(this.id).supportsBeaming, value); - } - public get manufacturerId(): MaybeNotKnown { return this.getValue(ManufacturerSpecificCCValues.manufacturerId.id); } @@ -932,13 +456,6 @@ export class ZWaveNode extends Endpoint } } - /** Which protocol is used to communicate with this node */ - public get protocol(): Protocols { - return isLongRangeNodeId(this.id) - ? Protocols.ZWaveLongRange - : Protocols.ZWave; - } - /** Whether a SUC return route was configured for this node */ public get hasSUCReturnRoute(): boolean { return !!this.driver.cacheGet( @@ -1040,61 +557,6 @@ export class ZWaveNode extends Endpoint this.driver.cacheSet(cacheKeys.node(this.id).deviceConfigHash, value); } - private _valueDB: ValueDB; - /** - * Provides access to this node's values - * @internal - */ - public get valueDB(): ValueDB { - return this._valueDB; - } - - /** - * Retrieves a stored value for a given value id. - * This does not request an updated value from the node! - */ - public getValue(valueId: ValueID): MaybeNotKnown { - return this._valueDB.getValue(valueId); - } - - /** - * Returns when the given value id was last updated by an update from the node. - */ - public getValueTimestamp(valueId: ValueID): MaybeNotKnown { - return this._valueDB.getTimestamp(valueId); - } - - /** - * Retrieves metadata for a given value id. - * This can be used to enhance the user interface of an application - */ - public getValueMetadata(valueId: ValueID): ValueMetadata { - // Check if a corresponding CC value is defined for this value ID - // so we can extend the returned metadata - const definedCCValues = getCCValues(valueId.commandClass); - let valueOptions: Required | undefined; - let meta: ValueMetadata | undefined; - if (definedCCValues) { - const value = Object.values(definedCCValues) - .find((v) => v?.is(valueId)); - if (value) { - if (typeof value !== "function") { - meta = value.meta; - } - valueOptions = value.options; - } - } - - const existingMetadata = this._valueDB.getMetadata(valueId); - return { - // The priority for returned metadata is valueDB > defined value > Any (default) - ...(existingMetadata ?? meta ?? ValueMetadata.Any), - // ...except for these flags, which are taken from defined values: - stateful: valueOptions?.stateful ?? defaultCCValueOptions.stateful, - secret: valueOptions?.secret ?? defaultCCValueOptions.secret, - }; - } - /** Returns a list of all value names that are defined on all endpoints of this node */ public getDefinedValueIDs(): TranslatedValueID[] { return nodeUtils.getDefinedValueIDs(this.driver, this); @@ -1395,280 +857,39 @@ export class ZWaveNode extends Endpoint }); } - /** - * @internal - * All polls that are currently scheduled for this node - */ - public scheduledPolls = new ObjectKeyMap(); + private _interviewAttempts: number = 0; + /** How many attempts to interview this node have already been made */ + public get interviewAttempts(): number { + return this._interviewAttempts; + } + + private _hasEmittedNoS2NetworkKeyError: boolean = false; + private _hasEmittedNoS0NetworkKeyError: boolean = false; /** - * @internal - * Schedules a value to be polled after a given time. Only one schedule can be active for a given value ID. - * @returns `true` if the poll was scheduled, `false` otherwise + * Starts or resumes a deferred initial interview of this node. + * + * **WARNING:** This is only allowed when the initial interview was deferred using the + * `interview.disableOnNodeAdded` option. Otherwise, this method will throw an error. + * + * **NOTE:** It is advised to NOT await this method as it can take a very long time (minutes to hours)! */ - public schedulePoll( - valueId: ValueID, - options: NodeSchedulePollOptions = {}, - ): boolean { - const { - timeoutMs = this.driver.options.timeouts.refreshValue, - expectedValue, - } = options; - - // Avoid false positives or false negatives due to a mis-formatted value ID - valueId = normalizeValueID(valueId); + public async interview(): Promise { + // The initial interview of the controller node is always done + // and cannot be deferred. + if (this.isControllerNode) return; - // Try to retrieve the corresponding CC API - const endpointInstance = this.getEndpoint(valueId.endpoint || 0); - if (!endpointInstance) return false; + if (!this.driver.options.interview?.disableOnNodeAdded) { + throw new ZWaveError( + `Calling ZWaveNode.interview() is not allowed because automatic node interviews are enabled. Wait for the driver to interview the node or use ZWaveNode.refreshInfo() to re-interview a node.`, + ZWaveErrorCodes.Driver_FeatureDisabled, + ); + } - const api = ( - (endpointInstance.commandClasses as any)[ - valueId.commandClass - ] as CCAPI - ).withOptions({ - // We do not want to delay more important communication by polling, so give it - // the lowest priority and don't retry unless overwritten by the options - maxSendAttempts: 1, - priority: MessagePriority.Poll, - }); + return this.driver.interviewNodeInternal(this); + } - // Check if the pollValue method is implemented - if (!api.pollValue) return false; - - // make sure there is only one timeout instance per poll - this.cancelScheduledPoll(valueId); - const timeout = setTimeout(async () => { - // clean up after the timeout - this.cancelScheduledPoll(valueId); - try { - await api.pollValue!.call(api, valueId); - } catch { - /* ignore */ - } - }, timeoutMs).unref(); - this.scheduledPolls.set(valueId, { timeout, expectedValue }); - - return true; - } - - /** - * @internal - * Cancels a poll that has been scheduled with schedulePoll. - * - * @param actualValue If given, this indicates the value that was received by a node, which triggered the poll to be canceled. - * If the scheduled poll expects a certain value and this matches the expected value for the scheduled poll, the poll will be canceled. - */ - public cancelScheduledPoll( - valueId: ValueID, - actualValue?: unknown, - ): boolean { - // Avoid false positives or false negatives due to a mis-formatted value ID - valueId = normalizeValueID(valueId); - - const poll = this.scheduledPolls.get(valueId); - if (!poll) return false; - - if ( - actualValue !== undefined - && poll.expectedValue !== undefined - && !isDeepStrictEqual(poll.expectedValue, actualValue) - ) { - return false; - } - - clearTimeout(poll.timeout); - this.scheduledPolls.delete(valueId); - - return true; - } - - public get endpointCountIsDynamic(): MaybeNotKnown { - return nodeUtils.endpointCountIsDynamic(this.driver, this); - } - - public get endpointsHaveIdenticalCapabilities(): MaybeNotKnown { - return nodeUtils.endpointsHaveIdenticalCapabilities(this.driver, this); - } - - public get individualEndpointCount(): MaybeNotKnown { - return nodeUtils.getIndividualEndpointCount(this.driver, this); - } - - public get aggregatedEndpointCount(): MaybeNotKnown { - return nodeUtils.getAggregatedEndpointCount(this.driver, this); - } - - /** Returns the device class of an endpoint. Falls back to the node's device class if the information is not known. */ - private getEndpointDeviceClass(index: number): MaybeNotKnown { - const deviceClass = this.getValue<{ - generic: number; - specific: number; - }>( - MultiChannelCCValues.endpointDeviceClass.endpoint( - this.endpointsHaveIdenticalCapabilities ? 1 : index, - ), - ); - if (deviceClass && this.deviceClass) { - return new DeviceClass( - this.deviceClass.basic, - deviceClass.generic, - deviceClass.specific, - ); - } - // fall back to the node's device class if it is known - return this.deviceClass; - } - - private getEndpointCCs(index: number): MaybeNotKnown { - const ret = this.getValue( - MultiChannelCCValues.endpointCCs.endpoint( - this.endpointsHaveIdenticalCapabilities ? 1 : index, - ), - ); - // Workaround for the change in #1977 - if (isArray(ret)) { - // The value is set up correctly, return it - return ret as CommandClasses[]; - } else if (isObject(ret) && "supportedCCs" in ret) { - return ret.supportedCCs as CommandClasses[]; - } - } - - /** - * Returns the current endpoint count of this node. - * - * If you want to enumerate the existing endpoints, use `getEndpointIndizes` instead. - * Some devices are known to contradict themselves. - */ - public getEndpointCount(): number { - return nodeUtils.getEndpointCount(this.driver, this); - } - - /** - * Returns indizes of all endpoints on the node. - */ - public getEndpointIndizes(): number[] { - return nodeUtils.getEndpointIndizes(this.driver, this); - } - - /** Whether the Multi Channel CC has been interviewed and all endpoint information is known */ - private get isMultiChannelInterviewComplete(): boolean { - return nodeUtils.isMultiChannelInterviewComplete(this.driver, this); - } - - /** Cache for this node's endpoint instances */ - private _endpointInstances = new Map(); - /** - * Returns an endpoint of this node with the given index. 0 returns the node itself. - */ - public getEndpoint(index: 0): Endpoint; - public getEndpoint(index: number): Endpoint | undefined; - public getEndpoint(index: number): Endpoint | undefined { - if (index < 0) { - throw new ZWaveError( - "The endpoint index must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - // Zero is the root endpoint - i.e. this node - if (index === 0) return this; - // Check if the Multi Channel CC interview for this node is completed, - // because we don't have all the information before that - if (!this.isMultiChannelInterviewComplete) { - this.driver.driverLog.print( - `Node ${this.id}, Endpoint ${index}: Trying to access endpoint instance before Multi Channel interview`, - "error", - ); - return undefined; - } - // Check if the endpoint index is in the list of known endpoint indizes - if (!this.getEndpointIndizes().includes(index)) return undefined; - - // Create an endpoint instance if it does not exist - if (!this._endpointInstances.has(index)) { - this._endpointInstances.set( - index, - new Endpoint( - this.id, - this.driver, - index, - this.getEndpointDeviceClass(index), - this.getEndpointCCs(index), - ), - ); - } - return this._endpointInstances.get(index)!; - } - - public getEndpointOrThrow(index: number): Endpoint { - const ret = this.getEndpoint(index); - if (!ret) { - throw new ZWaveError( - `Endpoint ${index} does not exist on Node ${this.id}`, - ZWaveErrorCodes.Controller_EndpointNotFound, - ); - } - return ret; - } - - /** Returns a list of all endpoints of this node, including the root endpoint (index 0) */ - public getAllEndpoints(): Endpoint[] { - return nodeUtils.getAllEndpoints(this.driver, this) as Endpoint[]; - } - - /** - * This tells us which interview stage was last completed - */ - - public get interviewStage(): InterviewStage { - return ( - this.driver.cacheGet(cacheKeys.node(this.id).interviewStage) - ?? InterviewStage.None - ); - } - public set interviewStage(value: InterviewStage) { - this.driver.cacheSet(cacheKeys.node(this.id).interviewStage, value); - } - - private _interviewAttempts: number = 0; - /** How many attempts to interview this node have already been made */ - public get interviewAttempts(): number { - return this._interviewAttempts; - } - - private _hasEmittedNoS2NetworkKeyError: boolean = false; - private _hasEmittedNoS0NetworkKeyError: boolean = false; - - /** Returns whether this node is the controller */ - public get isControllerNode(): boolean { - return this.id === this.driver.controller.ownNodeId; - } - - /** - * Starts or resumes a deferred initial interview of this node. - * - * **WARNING:** This is only allowed when the initial interview was deferred using the - * `interview.disableOnNodeAdded` option. Otherwise, this method will throw an error. - * - * **NOTE:** It is advised to NOT await this method as it can take a very long time (minutes to hours)! - */ - public async interview(): Promise { - // The initial interview of the controller node is always done - // and cannot be deferred. - if (this.isControllerNode) return; - - if (!this.driver.options.interview?.disableOnNodeAdded) { - throw new ZWaveError( - `Calling ZWaveNode.interview() is not allowed because automatic node interviews are enabled. Wait for the driver to interview the node or use ZWaveNode.refreshInfo() to re-interview a node.`, - ZWaveErrorCodes.Driver_FeatureDisabled, - ); - } - - return this.driver.interviewNodeInternal(this); - } - - private _refreshInfoPending: boolean = false; + private _refreshInfoPending: boolean = false; /** * Resets all information about this node and forces a fresh interview. @@ -1736,7 +957,7 @@ export class ZWaveNode extends Endpoint this._interviewAttempts = 0; this.interviewStage = InterviewStage.None; - this._ready = false; + this.ready = false; this.deviceClass = undefined; this.isListening = undefined; this.isFrequentListening = undefined; @@ -1762,9 +983,7 @@ export class ZWaveNode extends Endpoint this.statusMachine.restart(); // Remove queued polls that would interfere with the interview - for (const valueId of this.scheduledPolls.keys()) { - this.cancelScheduledPoll(valueId); - } + this.cancelAllScheduledPolls(); // Restore the previously saved name/location if (name != undefined) this.name = name; @@ -1918,7 +1137,7 @@ export class ZWaveNode extends Endpoint nodeId: this.id, }); const resp = await this.driver.sendMessage( - new GetNodeProtocolInfoRequest(this.driver, { + new GetNodeProtocolInfoRequest({ requestedNodeId: this.id, }), ); @@ -2078,7 +1297,7 @@ protocol version: ${this.protocolVersion}`; public async requestNodeInfo(): Promise { const resp = await this.driver.sendMessage< RequestNodeInfoResponse | ApplicationUpdateRequest - >(new RequestNodeInfoRequest(this.driver, { nodeId: this.id })); + >(new RequestNodeInfoRequest({ nodeId: this.id })); if (resp instanceof RequestNodeInfoResponse && !resp.wasSent) { // TODO: handle this in SendThreadMachine throw new ZWaveError( @@ -2171,7 +1390,6 @@ protocol version: ${this.protocolVersion}`; try { if (force) { instance = CommandClass.createInstanceUnchecked( - this.driver, endpoint, cc, )!; @@ -5113,6 +4331,8 @@ protocol version: ${this.protocolVersion}`; return; } + const ccVersion = getEffectiveCCVersion(this.driver, command); + // Look up the received notification in the config const notification = getNotification(command.notificationType); @@ -5265,7 +4485,7 @@ protocol version: ${this.protocolVersion}`; ); valueId = unknownValue.endpoint(command.endpointIndex); - if (command.version >= 2) { + if (ccVersion >= 2) { if (!this.valueDB.hasMetadata(valueId)) { this.valueDB.setMetadata(valueId, unknownValue.meta); } @@ -5318,7 +4538,7 @@ protocol version: ${this.protocolVersion}`; const valueId = unknownValue.endpoint(command.endpointIndex); // Make sure the metdata exists - if (command.version >= 2) { + if (ccVersion >= 2) { if (!this.valueDB.hasMetadata(valueId)) { this.valueDB.setMetadata(valueId, unknownValue.meta); } @@ -5464,1078 +4684,163 @@ protocol version: ${this.protocolVersion}`; } this.busySettingClock = false; } - } - - private async handleTimeGet(command: TimeCCTimeGet): Promise { - const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - - const now = new Date(); - const hours = now.getHours(); - const minutes = now.getMinutes(); - const seconds = now.getSeconds(); - - try { - // We are being queried, so the device may actually not support the CC, just control it. - // Using the commandClasses property would throw in that case - const api = endpoint - .createAPI(CommandClasses.Time, false) - .withOptions({ - // Answer with the same encapsulation as asked, but omit - // Supervision as it shouldn't be used for Get-Report flows - encapsulationFlags: command.encapsulationFlags - & ~EncapsulationFlags.Supervision, - }); - await api.reportTime(hours, minutes, seconds); - } catch (e: any) { - this.driver.controllerLog.logNode(this.id, { - message: e.message, - level: "error", - }); - // ignore - } - } - - private async handleDateGet(command: TimeCCDateGet): Promise { - const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - - const now = new Date(); - const year = now.getFullYear(); - const month = now.getMonth() + 1; - const day = now.getDate(); - - try { - // We are being queried, so the device may actually not support the CC, just control it. - // Using the commandClasses property would throw in that case - const api = endpoint - .createAPI(CommandClasses.Time, false) - .withOptions({ - // Answer with the same encapsulation as asked, but omit - // Supervision as it shouldn't be used for Get-Report flows - encapsulationFlags: command.encapsulationFlags - & ~EncapsulationFlags.Supervision, - }); - await api.reportDate(year, month, day); - } catch (e: any) { - this.driver.controllerLog.logNode(this.id, { - message: e.message, - level: "error", - }); - // ignore - } - } - - private async handleTimeOffsetGet( - command: TimeCCTimeOffsetGet, - ): Promise { - const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - - const timezone = getDSTInfo(new Date()); - - try { - // We are being queried, so the device may actually not support the CC, just control it. - // Using the commandClasses property would throw in that case - const api = endpoint - .createAPI(CommandClasses.Time, false) - .withOptions({ - // Answer with the same encapsulation as asked, but omit - // Supervision as it shouldn't be used for Get-Report flows - encapsulationFlags: command.encapsulationFlags - & ~EncapsulationFlags.Supervision, - }); - await api.reportTimezone(timezone); - } catch { - // ignore - } - } - - /** - * Retrieves the firmware update capabilities of a node to decide which options to offer a user prior to the update. - * This method uses cached information from the most recent interview. - */ - public getFirmwareUpdateCapabilitiesCached(): FirmwareUpdateCapabilities { - const firmwareUpgradable = this.getValue( - FirmwareUpdateMetaDataCCValues.firmwareUpgradable.id, - ); - const supportsActivation = this.getValue( - FirmwareUpdateMetaDataCCValues.supportsActivation.id, - ); - const continuesToFunction = this.getValue( - FirmwareUpdateMetaDataCCValues.continuesToFunction.id, - ); - const additionalFirmwareIDs = this.getValue( - FirmwareUpdateMetaDataCCValues.additionalFirmwareIDs.id, - ); - const supportsResuming = this.getValue( - FirmwareUpdateMetaDataCCValues.supportsResuming.id, - ); - const supportsNonSecureTransfer = this.getValue( - FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer.id, - ); - - // Ensure all information was queried - if ( - !firmwareUpgradable - || !isArray(additionalFirmwareIDs) - ) { - return { firmwareUpgradable: false }; - } - - return { - firmwareUpgradable: true, - // TODO: Targets are not the list of IDs - maybe expose the IDs as well? - firmwareTargets: new Array(1 + additionalFirmwareIDs.length).fill(0) - .map((_, i) => i), - continuesToFunction, - supportsActivation, - supportsResuming, - supportsNonSecureTransfer, - }; - } - - /** - * Retrieves the firmware update capabilities of a node to decide which options to offer a user prior to the update. - * This communicates with the node to retrieve fresh information. - */ - public async getFirmwareUpdateCapabilities(): Promise< - FirmwareUpdateCapabilities - > { - const api = this.commandClasses["Firmware Update Meta Data"]; - const meta = await api.getMetaData(); - if (!meta) { - throw new ZWaveError( - `Failed to request firmware update capabilities: The node did not respond in time!`, - ZWaveErrorCodes.Controller_NodeTimeout, - ); - } else if (!meta.firmwareUpgradable) { - return { - firmwareUpgradable: false, - }; - } - - return { - firmwareUpgradable: true, - // TODO: Targets are not the list of IDs - maybe expose the IDs as well? - firmwareTargets: new Array(1 + meta.additionalFirmwareIDs.length) - .fill(0).map((_, i) => i), - continuesToFunction: meta.continuesToFunction, - supportsActivation: meta.supportsActivation, - supportsResuming: meta.supportsResuming, - supportsNonSecureTransfer: meta.supportsNonSecureTransfer, - }; - } - - private _abortFirmwareUpdate: (() => Promise) | undefined; - /** - * Aborts an active firmware update process - */ - public async abortFirmwareUpdate(): Promise { - if (!this._abortFirmwareUpdate) return; - await this._abortFirmwareUpdate(); - } - - // Stores the CRC of the previously transferred firmware image. - // Allows detecting whether resuming is supported and where to continue in a multi-file transfer. - private _previousFirmwareCRC: number | undefined; - - /** Is used to remember fragment requests that came in before they were able to be handled */ - private _firmwareUpdatePrematureRequest: - | FirmwareUpdateMetaDataCCGet - | undefined; - - /** - * Performs an OTA firmware upgrade of one or more chips on this node. - * - * This method will resolve after the process has **COMPLETED**. Failure to start any one of the provided updates will throw an error. - * - * **WARNING: Use at your own risk! We don't take any responsibility if your devices don't work after an update.** - * - * @param updates An array of firmware updates that will be done in sequence - * - * @returns Whether all of the given updates were successful. - */ - public async updateFirmware( - updates: Firmware[], - options: FirmwareUpdateOptions = {}, - ): Promise { - if (updates.length === 0) { - throw new ZWaveError( - `At least one update must be provided`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - // Check that each update has a buffer with at least 1 byte - if (updates.some((u) => u.data.length === 0)) { - throw new ZWaveError( - `All firmware updates must have a non-empty data buffer`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - // Check that the targets are not duplicates - if ( - distinct(updates.map((u) => u.firmwareTarget ?? 0)).length - !== updates.length - ) { - throw new ZWaveError( - `The target of all provided firmware updates must be unique`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - // Don't start the process twice - if (this.driver.controller.isFirmwareUpdateInProgress()) { - throw new ZWaveError( - `Failed to start the update: An OTW upgrade of the controller is in progress!`, - ZWaveErrorCodes.FirmwareUpdateCC_Busy, - ); - } - - // Don't allow starting two firmware updates for the same node - const task = this.getUpdateFirmwareTask(updates, options); - if (task instanceof Promise) { - throw new ZWaveError( - `Failed to start the update: A firmware update is already in progress for this node!`, - ZWaveErrorCodes.FirmwareUpdateCC_Busy, - ); - } - - // Queue the task - return this.driver.scheduler.queueTask(task); - } - - /** - * Returns whether a firmware update is in progress for this node. - */ - public isFirmwareUpdateInProgress(): boolean { - return !!this.driver.scheduler.findTask( - nodeUtils.isFirmwareUpdateOTATask, - ); - } - - private getUpdateFirmwareTask( - updates: Firmware[], - options: FirmwareUpdateOptions = {}, - ): Promise | TaskBuilder { - const self = this; - - // This task should only run once at a time - const existingTask = this.driver.scheduler.findTask< - FirmwareUpdateResult - >((t) => - t.tag?.id === "firmware-update-ota" - && t.tag.nodeId === self.id - ); - if (existingTask) return existingTask; - - let keepAwake: boolean; - - return { - // Firmware updates cause a lot of traffic. Execute them in the background. - priority: TaskPriority.Lower, - tag: { id: "firmware-update-ota", nodeId: self.id }, - task: async function* firmwareUpdateTask() { - // Keep battery powered nodes awake during the process - keepAwake = self.keepAwake; - self.keepAwake = true; - - // Support aborting the update - const abortContext = { - abort: false, - tooLateToAbort: false, - abortPromise: createDeferredPromise(), - }; - - self._abortFirmwareUpdate = async () => { - if (abortContext.tooLateToAbort) { - throw new ZWaveError( - `The firmware update was transmitted completely, cannot abort anymore.`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort, - ); - } - - self.driver.controllerLog.logNode(self.id, { - message: `Aborting firmware update...`, - direction: "outbound", - }); - - // Trigger the abort - abortContext.abort = true; - const aborted = await abortContext.abortPromise; - if (!aborted) { - throw new ZWaveError( - `The node did not acknowledge the aborted update`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort, - ); - } - self.driver.controllerLog.logNode(self.id, { - message: `Firmware update aborted`, - direction: "inbound", - }); - }; - - // Prepare the firmware update - let fragmentSizeSecure: number; - let fragmentSizeNonSecure: number; - let meta: FirmwareUpdateMetaData; - try { - const prepareResult = await self - .prepareFirmwareUpdateInternal( - updates.map((u) => u.firmwareTarget ?? 0), - abortContext, - ); - - // Handle early aborts - if (abortContext.abort) { - const result: FirmwareUpdateResult = { - success: false, - status: FirmwareUpdateStatus - .Error_TransmissionFailed, - reInterview: false, - }; - self.emit( - "firmware update finished", - self, - result, - ); - return result; - } - - // If the firmware update was not aborted, prepareResult is definitely defined - ({ - fragmentSizeSecure, - fragmentSizeNonSecure, - ...meta - } = prepareResult!); - } catch { - // Not sure what the error is, but we'll label it "transmission failed" - const result: FirmwareUpdateResult = { - success: false, - status: FirmwareUpdateStatus.Error_TransmissionFailed, - reInterview: false, - }; - - return result; - } - - yield; // Give the task scheduler time to do something else - - // The resume and non-secure transfer features may not be supported by the node - // If not, disable them, even though the application requested them - if (!meta.supportsResuming) options.resume = false; - - const securityClass = self.getHighestSecurityClass(); - const isSecure = securityClass === SecurityClass.S0_Legacy - || securityClassIsS2(securityClass); - if (!isSecure) { - // The nonSecureTransfer option is only relevant for secure devices - options.nonSecureTransfer = false; - } else if (!meta.supportsNonSecureTransfer) { - options.nonSecureTransfer = false; - } - - // Throttle the progress emitter so applications can handle the load of events - const notifyProgress = throttle( - (progress) => - self.emit( - "firmware update progress", - self, - progress, - ), - 250, - true, - ); - - // If resuming is supported and desired, try to figure out with which file to continue - const updatesWithChecksum = updates.map((u) => ({ - ...u, - checksum: CRC16_CCITT(u.data), - })); - let skipFinishedFiles = -1; - let shouldResume = options.resume - && self._previousFirmwareCRC != undefined; - if (shouldResume) { - skipFinishedFiles = updatesWithChecksum.findIndex( - (u) => u.checksum === self._previousFirmwareCRC, - ); - if (skipFinishedFiles === -1) shouldResume = false; - } - - // Perform all firmware updates in sequence - let updateResult!: PartialFirmwareUpdateResult; - let conservativeWaitTime: number; - - const totalBytes: number = updatesWithChecksum.reduce( - (total, update) => total + update.data.length, - 0, - ); - let sentBytesOfPreviousFiles = 0; - - for (let i = 0; i < updatesWithChecksum.length; i++) { - const { firmwareTarget: target = 0, data, checksum } = - updatesWithChecksum[i]; - - if (i < skipFinishedFiles) { - // If we are resuming, skip this file since it was already done before - self.driver.controllerLog.logNode( - self.id, - `Skipping already completed firmware update (part ${ - i + 1 - } / ${updatesWithChecksum.length})...`, - ); - sentBytesOfPreviousFiles += data.length; - continue; - } - - self.driver.controllerLog.logNode( - self.id, - `Updating firmware (part ${ - i + 1 - } / ${updatesWithChecksum.length})...`, - ); - - // For determining the initial fragment size, assume the node respects our choice. - // If the node is not secure, these two values are identical anyways. - let fragmentSize = options.nonSecureTransfer - ? fragmentSizeNonSecure - : fragmentSizeSecure; - - // Tell the node to start requesting fragments - const { resume, nonSecureTransfer } = yield* self - .beginFirmwareUpdateInternal( - data, - target, - meta, - fragmentSize, - checksum, - shouldResume, - options.nonSecureTransfer, - ); - - // If the node did not accept non-secure transfer, revisit our choice of fragment size - if (options.nonSecureTransfer && !nonSecureTransfer) { - fragmentSize = fragmentSizeSecure; - } - - // Remember the checksum, so we can resume if necessary - self._previousFirmwareCRC = checksum; - - if (shouldResume) { - self.driver.controllerLog.logNode( - self.id, - `Node ${ - resume ? "accepted" : "did not accept" - } resuming the update...`, - ); - } - if (nonSecureTransfer) { - self.driver.controllerLog.logNode( - self.id, - `Firmware will be transferred without encryption...`, - ); - } - - yield; // Give the task scheduler time to do something else - - // Listen for firmware update fragment requests and handle them - updateResult = yield* self.doFirmwareUpdateInternal( - data, - fragmentSize, - nonSecureTransfer, - abortContext, - (fragment, total) => { - const progress: FirmwareUpdateProgress = { - currentFile: i + 1, - totalFiles: updatesWithChecksum.length, - sentFragments: fragment, - totalFragments: total, - progress: roundTo( - ( - (sentBytesOfPreviousFiles - + Math.min( - fragment * fragmentSize, - data.length, - )) - / totalBytes - ) * 100, - 2, - ), - }; - notifyProgress(progress); - - // When this file is done, add the fragments to the total, so we can compute the total progress correctly - if (fragment === total) { - sentBytesOfPreviousFiles += data.length; - } - }, - ); - - // If we wait, wait a bit longer than the device told us, so it is actually ready to use - conservativeWaitTime = self.driver - .getConservativeWaitTimeAfterFirmwareUpdate( - updateResult.waitTime, - ); - - if (!updateResult.success) { - self.driver.controllerLog.logNode(self.id, { - message: `Firmware update (part ${ - i + 1 - } / ${updatesWithChecksum.length}) failed with status ${ - getEnumMemberName( - FirmwareUpdateStatus, - updateResult.status, - ) - }`, - direction: "inbound", - }); - - const result: FirmwareUpdateResult = { - ...updateResult, - waitTime: undefined, - reInterview: false, - }; - self.emit( - "firmware update finished", - self, - result, - ); - - return result; - } else if (i < updatesWithChecksum.length - 1) { - // Update succeeded, but we're not done yet - - self.driver.controllerLog.logNode(self.id, { - message: `Firmware update (part ${ - i + 1 - } / ${updatesWithChecksum.length}) succeeded with status ${ - getEnumMemberName( - FirmwareUpdateStatus, - updateResult.status, - ) - }`, - direction: "inbound", - }); - - self.driver.controllerLog.logNode( - self.id, - `Continuing with next part in ${conservativeWaitTime} seconds...`, - ); - - // If we've resumed the previous file, there's no need to resume the next one too - shouldResume = false; - - yield () => wait(conservativeWaitTime * 1000, true); - } - } - - // We're done. No need to resume this update - self._previousFirmwareCRC = undefined; - - const result: FirmwareUpdateResult = { - ...updateResult, - waitTime: conservativeWaitTime!, - reInterview: true, - }; - - // After a successful firmware update, we want to interview sleeping nodes immediately, - // so don't send them to sleep when they wake up - keepAwake = true; - - self.emit("firmware update finished", self, result); - - return result; - }, - cleanup() { - self._abortFirmwareUpdate = undefined; - self._firmwareUpdatePrematureRequest = undefined; - - // Make sure that the keepAwake flag gets reset at the end - self.keepAwake = keepAwake; - if (!keepAwake) { - setImmediate(() => { - self.driver.debounceSendNodeToSleep(self); - }); - } - - return Promise.resolve(); - }, - }; - } - - /** Prepares the firmware update of a single target by collecting the necessary information */ - private async prepareFirmwareUpdateInternal( - targets: number[], - abortContext: AbortFirmwareUpdateContext, - ): Promise< - | undefined - | (FirmwareUpdateMetaData & { - fragmentSizeSecure: number; - fragmentSizeNonSecure: number; - }) - > { - const api = this.commandClasses["Firmware Update Meta Data"]; - - // ================================ - // STEP 1: - // Check if this update is possible - const meta = await api.getMetaData(); - if (!meta) { - throw new ZWaveError( - `Failed to start the update: The node did not respond in time!`, - ZWaveErrorCodes.Controller_NodeTimeout, - ); - } - - for (const target of targets) { - if (target === 0) { - if (!meta.firmwareUpgradable) { - throw new ZWaveError( - `Failed to start the update: The Z-Wave chip firmware is not upgradable!`, - ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable, - ); - } - } else { - if (api.version < 3) { - throw new ZWaveError( - `Failed to start the update: The node does not support upgrading a different firmware target than 0!`, - ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound, - ); - } else if ( - meta.additionalFirmwareIDs[target - 1] == undefined - ) { - throw new ZWaveError( - `Failed to start the update: Firmware target #${target} not found on this node!`, - ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound, - ); - } - } - } - - // ================================ - // STEP 2: - // Determine the fragment size - const fcc = new FirmwareUpdateMetaDataCC(this.driver, { - nodeId: this.id, - }); - const maxGrossPayloadSizeSecure = this.driver - .computeNetCCPayloadSize( - fcc, - ); - const maxGrossPayloadSizeNonSecure = this.driver - .computeNetCCPayloadSize(fcc, true); - - const maxNetPayloadSizeSecure = maxGrossPayloadSizeSecure - - 2 // report number - - (fcc.version >= 2 ? 2 : 0); // checksum - const maxNetPayloadSizeNonSecure = maxGrossPayloadSizeNonSecure - - 2 // report number - - (fcc.version >= 2 ? 2 : 0); // checksum - - // Use the smallest allowed payload - const fragmentSizeSecure = Math.min( - maxNetPayloadSizeSecure, - meta.maxFragmentSize ?? Number.POSITIVE_INFINITY, - ); - const fragmentSizeNonSecure = Math.min( - maxNetPayloadSizeNonSecure, - meta.maxFragmentSize ?? Number.POSITIVE_INFINITY, - ); - - if (abortContext.abort) { - abortContext.abortPromise.resolve(true); - return; - } else { - return { - ...meta, - fragmentSizeSecure, - fragmentSizeNonSecure, - }; - } - } - - protected async handleUnexpectedFirmwareUpdateGet( - command: FirmwareUpdateMetaDataCCGet, - ): Promise { - // This method will only be called under two circumstances: - // 1. The node is currently busy responding to a firmware update request -> remember the request - if (this.isFirmwareUpdateInProgress()) { - this._firmwareUpdatePrematureRequest = command; - return; - } + } - // 2. No firmware update is in progress -> abort - this.driver.controllerLog.logNode(this.id, { - message: - `Received Firmware Update Get, but no firmware update is in progress. Forcing the node to abort...`, - direction: "inbound", - }); + private async handleTimeGet(command: TimeCCTimeGet): Promise { + const endpoint = this.getEndpoint(command.endpointIndex) ?? this; + + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); - // Since no update is in progress, we need to determine the fragment size again - const fcc = new FirmwareUpdateMetaDataCC(this.driver, { - nodeId: this.id, - }); - const fragmentSize = this.driver.computeNetCCPayloadSize(fcc) - - 2 // report number - - (fcc.version >= 2 ? 2 : 0); // checksum - const fragment = randomBytes(fragmentSize); try { - await this.sendCorruptedFirmwareUpdateReport( - command.reportNumber, - fragment, - ); - } catch { + // We are being queried, so the device may actually not support the CC, just control it. + // Using the commandClasses property would throw in that case + const api = endpoint + .createAPI(CommandClasses.Time, false) + .withOptions({ + // Answer with the same encapsulation as asked, but omit + // Supervision as it shouldn't be used for Get-Report flows + encapsulationFlags: command.encapsulationFlags + & ~EncapsulationFlags.Supervision, + }); + await api.reportTime(hours, minutes, seconds); + } catch (e: any) { + this.driver.controllerLog.logNode(this.id, { + message: e.message, + level: "error", + }); // ignore } } - /** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */ - private *beginFirmwareUpdateInternal( - data: Buffer, - target: number, - meta: FirmwareUpdateMetaData, - fragmentSize: number, - checksum: number, - resume: boolean | undefined, - nonSecureTransfer: boolean | undefined, - ) { - const api = this.commandClasses["Firmware Update Meta Data"]; + private async handleDateGet(command: TimeCCDateGet): Promise { + const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - // ================================ - // STEP 3: - // Start the update - this.driver.controllerLog.logNode(this.id, { - message: `Starting firmware update...`, - direction: "outbound", - }); + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth() + 1; + const day = now.getDate(); - // Request the node to start the upgrade. Pause the task until this is done, - // since the call can block for a long time - const result: FirmwareUpdateInitResult = yield () => - api.requestUpdate({ - // TODO: Should manufacturer id and firmware id be provided externally? - manufacturerId: meta.manufacturerId, - firmwareId: target == 0 - ? meta.firmwareId - : meta.additionalFirmwareIDs[target - 1], - firmwareTarget: target, - fragmentSize, - checksum, - resume, - nonSecureTransfer, + try { + // We are being queried, so the device may actually not support the CC, just control it. + // Using the commandClasses property would throw in that case + const api = endpoint + .createAPI(CommandClasses.Time, false) + .withOptions({ + // Answer with the same encapsulation as asked, but omit + // Supervision as it shouldn't be used for Get-Report flows + encapsulationFlags: command.encapsulationFlags + & ~EncapsulationFlags.Supervision, + }); + await api.reportDate(year, month, day); + } catch (e: any) { + this.driver.controllerLog.logNode(this.id, { + message: e.message, + level: "error", }); - switch (result.status) { - case FirmwareUpdateRequestStatus.Error_AuthenticationExpected: - throw new ZWaveError( - `Failed to start the update: A manual authentication event (e.g. button push) was expected!`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, - ); - case FirmwareUpdateRequestStatus.Error_BatteryLow: - throw new ZWaveError( - `Failed to start the update: The battery level is too low!`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, - ); - case FirmwareUpdateRequestStatus - .Error_FirmwareUpgradeInProgress: - throw new ZWaveError( - `Failed to start the update: A firmware upgrade is already in progress!`, - ZWaveErrorCodes.FirmwareUpdateCC_Busy, - ); - case FirmwareUpdateRequestStatus - .Error_InvalidManufacturerOrFirmwareID: - throw new ZWaveError( - `Failed to start the update: Invalid manufacturer or firmware id!`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, - ); - case FirmwareUpdateRequestStatus.Error_InvalidHardwareVersion: - throw new ZWaveError( - `Failed to start the update: Invalid hardware version!`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, - ); - case FirmwareUpdateRequestStatus.Error_NotUpgradable: - throw new ZWaveError( - `Failed to start the update: Firmware target #${target} is not upgradable!`, - ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable, - ); - case FirmwareUpdateRequestStatus.Error_FragmentSizeTooLarge: - throw new ZWaveError( - `Failed to start the update: The chosen fragment size is too large!`, - ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, - ); - case FirmwareUpdateRequestStatus.OK: - // All good, we have started! - // Keep the node awake until the update is done. - this.keepAwake = true; + // ignore } - - return { - resume: !!result.resume, - nonSecureTransfer: !!result.nonSecureTransfer, - }; } - protected async handleFirmwareUpdateMetaDataGet( - command: FirmwareUpdateMetaDataCCMetaDataGet, + private async handleTimeOffsetGet( + command: TimeCCTimeOffsetGet, ): Promise { - const endpoint = this.getEndpoint(command.endpointIndex) - ?? this; - - // We are being queried, so the device may actually not support the CC, just control it. - // Using the commandClasses property would throw in that case - const api = endpoint - .createAPI(CommandClasses["Firmware Update Meta Data"], false) - .withOptions({ - // Answer with the same encapsulation as asked, but omit - // Supervision as it shouldn't be used for Get-Report flows - encapsulationFlags: command.encapsulationFlags - & ~EncapsulationFlags.Supervision, - }); + const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - // We do not support the firmware to be upgraded. - await api.reportMetaData({ - manufacturerId: this.driver.options.vendor?.manufacturerId - ?? 0xffff, - firmwareUpgradable: false, - hardwareVersion: this.driver.options.vendor?.hardwareVersion - ?? 0, - }); - } + const timezone = getDSTInfo(new Date()); - private async sendCorruptedFirmwareUpdateReport( - reportNum: number, - fragment: Buffer, - nonSecureTransfer: boolean = false, - ): Promise { try { - await this.commandClasses["Firmware Update Meta Data"] + // We are being queried, so the device may actually not support the CC, just control it. + // Using the commandClasses property would throw in that case + const api = endpoint + .createAPI(CommandClasses.Time, false) .withOptions({ - // Only encapsulate if the transfer is secure - autoEncapsulate: !nonSecureTransfer, - }) - .sendFirmwareFragment(reportNum, true, fragment); + // Answer with the same encapsulation as asked, but omit + // Supervision as it shouldn't be used for Get-Report flows + encapsulationFlags: command.encapsulationFlags + & ~EncapsulationFlags.Supervision, + }); + await api.reportTimezone(timezone); } catch { // ignore } } - private hasPendingFirmwareUpdateFragment( - fragmentNumber: number, - ): boolean { - // Avoid queuing duplicate fragments - const isCurrentFirmwareFragment = (t: Transaction) => - t.message.getNodeId() === this.id - && isCommandClassContainer(t.message) - && t.message.command instanceof FirmwareUpdateMetaDataCCReport - && t.message.command.reportNumber === fragmentNumber; - - return this.driver.hasPendingTransactions( - isCurrentFirmwareFragment, + /** + * Retrieves the firmware update capabilities of a node to decide which options to offer a user prior to the update. + * This method uses cached information from the most recent interview. + */ + public getFirmwareUpdateCapabilitiesCached(): FirmwareUpdateCapabilities { + const firmwareUpgradable = this.getValue( + FirmwareUpdateMetaDataCCValues.firmwareUpgradable.id, + ); + const supportsActivation = this.getValue( + FirmwareUpdateMetaDataCCValues.supportsActivation.id, + ); + const continuesToFunction = this.getValue( + FirmwareUpdateMetaDataCCValues.continuesToFunction.id, + ); + const additionalFirmwareIDs = this.getValue( + FirmwareUpdateMetaDataCCValues.additionalFirmwareIDs.id, + ); + const supportsResuming = this.getValue( + FirmwareUpdateMetaDataCCValues.supportsResuming.id, + ); + const supportsNonSecureTransfer = this.getValue( + FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer.id, ); - } - - private async *doFirmwareUpdateInternal( - data: Buffer, - fragmentSize: number, - nonSecureTransfer: boolean, - abortContext: AbortFirmwareUpdateContext, - onProgress: (fragment: number, total: number) => void, - ): AsyncGenerator< - any, - PartialFirmwareUpdateResult, - any - > { - const numFragments = Math.ceil(data.length / fragmentSize); - - // Make sure we're not responding to an outdated request immediately - this._firmwareUpdatePrematureRequest = undefined; - - // ================================ - // STEP 4: - // Respond to fragment requests from the node - update: while (true) { - yield; // Give the task scheduler time to do something else - - // During ongoing firmware updates, it can happen that the next request is received before the callback for the previous response - // is back. In that case we can immediately handle the premature request. Otherwise wait for the next request. - let fragmentRequest: FirmwareUpdateMetaDataCCGet; - if (this._firmwareUpdatePrematureRequest) { - fragmentRequest = this._firmwareUpdatePrematureRequest; - this._firmwareUpdatePrematureRequest = undefined; - } else { - try { - fragmentRequest = yield () => - this.driver - .waitForCommand( - (cc) => - cc.nodeId === this.id - && cc - instanceof FirmwareUpdateMetaDataCCGet, - // Wait up to 2 minutes for each fragment request. - // Some users try to update devices with unstable connections, where 30s can be too short. - timespan.minutes(2), - ); - } catch { - // In some cases it can happen that the device stops requesting update frames - // We need to timeout the update in this case so it can be restarted - this.driver.controllerLog.logNode(this.id, { - message: `Firmware update timed out`, - direction: "none", - level: "warn", - }); - - return { - success: false, - status: FirmwareUpdateStatus.Error_Timeout, - }; - } - } - - // When a node requests a firmware update fragment, it must be awake - this.markAsAwake(); - - if (fragmentRequest.reportNumber > numFragments) { - this.driver.controllerLog.logNode(this.id, { - message: - `Received Firmware Update Get for an out-of-bounds fragment. Forcing the node to abort...`, - direction: "inbound", - }); - await this.sendCorruptedFirmwareUpdateReport( - fragmentRequest.reportNumber, - randomBytes(fragmentSize), - nonSecureTransfer, - ); - // This will cause the node to abort the process, wait for that - break update; - } - - // Actually send the requested frames - request: for ( - let num = fragmentRequest.reportNumber; - num - < fragmentRequest.reportNumber - + fragmentRequest.numReports; - num++ - ) { - yield; // Give the task scheduler time to do something else - - // Check if the node requested more fragments than are left - if (num > numFragments) { - break; - } - const fragment = data.subarray( - (num - 1) * fragmentSize, - num * fragmentSize, - ); - - if (abortContext.abort) { - await this.sendCorruptedFirmwareUpdateReport( - fragmentRequest.reportNumber, - randomBytes(fragment.length), - nonSecureTransfer, - ); - // This will cause the node to abort the process, wait for that - break update; - } else { - // Avoid queuing duplicate fragments - if (this.hasPendingFirmwareUpdateFragment(num)) { - this.driver.controllerLog.logNode(this.id, { - message: `Firmware fragment ${num} already queued`, - level: "warn", - }); - continue request; - } - this.driver.controllerLog.logNode(this.id, { - message: - `Sending firmware fragment ${num} / ${numFragments}`, - direction: "outbound", - }); - const isLast = num === numFragments; - - try { - await this - .commandClasses["Firmware Update Meta Data"] - .withOptions({ - // Only encapsulate if the transfer is secure - autoEncapsulate: !nonSecureTransfer, - }) - .sendFirmwareFragment(num, isLast, fragment); - - onProgress(num, numFragments); - - // If that was the last one wait for status report from the node and restart interview - if (isLast) { - abortContext.tooLateToAbort = true; - break update; - } - } catch { - // When transmitting fails, simply stop responding to this request and wait for the node to re-request the fragment - this.driver.controllerLog.logNode(this.id, { - message: - `Failed to send firmware fragment ${num} / ${numFragments}`, - direction: "outbound", - level: "warn", - }); - break request; - } - } - } + // Ensure all information was queried + if ( + !firmwareUpgradable + || !isArray(additionalFirmwareIDs) + ) { + return { firmwareUpgradable: false }; } - yield; // Give the task scheduler time to do something else - - // ================================ - // STEP 5: - // Finalize the update process - - const statusReport: - | FirmwareUpdateMetaDataCCStatusReport - | undefined = yield () => - this.driver - .waitForCommand( - (cc) => - cc.nodeId === this.id - && cc - instanceof FirmwareUpdateMetaDataCCStatusReport, - // Wait up to 5 minutes. It should never take that long, but the specs - // don't say anything specific - 5 * 60000, - ) - .catch(() => undefined); - - if (abortContext.abort) { - abortContext.abortPromise.resolve( - // The error should be Error_TransmissionFailed, but some devices - // use the Error_Checksum error code instead - statusReport?.status - === FirmwareUpdateStatus.Error_TransmissionFailed - || statusReport?.status - === FirmwareUpdateStatus.Error_Checksum, - ); - } + return { + firmwareUpgradable: true, + // TODO: Targets are not the list of IDs - maybe expose the IDs as well? + firmwareTargets: new Array(1 + additionalFirmwareIDs.length).fill(0) + .map((_, i) => i), + continuesToFunction, + supportsActivation, + supportsResuming, + supportsNonSecureTransfer, + }; + } - if (!statusReport) { - this.driver.controllerLog.logNode( - this.id, - `The node did not acknowledge the completed update`, - "warn", + /** + * Retrieves the firmware update capabilities of a node to decide which options to offer a user prior to the update. + * This communicates with the node to retrieve fresh information. + */ + public async getFirmwareUpdateCapabilities(): Promise< + FirmwareUpdateCapabilities + > { + const api = this.commandClasses["Firmware Update Meta Data"]; + const meta = await api.getMetaData(); + if (!meta) { + throw new ZWaveError( + `Failed to request firmware update capabilities: The node did not respond in time!`, + ZWaveErrorCodes.Controller_NodeTimeout, ); - + } else if (!meta.firmwareUpgradable) { return { - success: false, - status: FirmwareUpdateStatus.Error_Timeout, + firmwareUpgradable: false, }; } - const { status, waitTime } = statusReport; - - // Actually, OK_WaitingForActivation should never happen since we don't allow - // delayed activation in the RequestGet command - const success = status >= FirmwareUpdateStatus.OK_WaitingForActivation; - return { - success, - status, - waitTime, + firmwareUpgradable: true, + // TODO: Targets are not the list of IDs - maybe expose the IDs as well? + firmwareTargets: new Array(1 + meta.additionalFirmwareIDs.length) + .fill(0).map((_, i) => i), + continuesToFunction: meta.continuesToFunction, + supportsActivation: meta.supportsActivation, + supportsResuming: meta.supportsResuming, + supportsNonSecureTransfer: meta.supportsNonSecureTransfer, }; } @@ -6596,50 +4901,6 @@ protocol version: ${this.protocolVersion}`; } } - /** - * Whether the node should be kept awake when there are no pending messages. - */ - public keepAwake: boolean = false; - - private isSendingNoMoreInformation: boolean = false; - /** - * @internal - * Sends the node a WakeUpCCNoMoreInformation so it can go back to sleep - */ - public async sendNoMoreInformation(): Promise { - // Don't send the node back to sleep if it should be kept awake - if (this.keepAwake) return false; - - // Avoid calling this method more than once - if (this.isSendingNoMoreInformation) return false; - this.isSendingNoMoreInformation = true; - - let msgSent = false; - if ( - this.status === NodeStatus.Awake - && this.interviewStage === InterviewStage.Complete - ) { - this.driver.controllerLog.logNode(this.id, { - message: "Sending node back to sleep...", - direction: "outbound", - }); - try { - // it is important that we catch errors in this call - // otherwise, this method will not work anymore because - // isSendingNoMoreInformation is stuck on `true` - await this.commandClasses["Wake Up"].sendNoMoreInformation(); - msgSent = true; - } catch { - /* ignore */ - } finally { - this.markAsAsleep(); - } - } - - this.isSendingNoMoreInformation = false; - return msgSent; - } - /** * Instructs the node to send powerlevel test frames to the other node using the given powerlevel. Returns how many frames were acknowledged during the test. * @@ -8103,7 +6364,6 @@ ${formatRouteHealthCheckSummary(this.id, otherNode.id, summary)}`, : undefined; const ccInstance = CommandClass.createInstanceUnchecked( - this.driver, this, valueId.commandClass, ); @@ -8167,4 +6427,25 @@ ${formatRouteHealthCheckSummary(this.id, otherNode.id, summary)}`, return ret; } + + protected _emit( + event: TEvent, + ...args: Parameters + ): boolean { + return this.emit(event, ...args); + } + + protected _on( + event: TEvent, + callback: AllNodeEvents[TEvent], + ): this { + return this.on(event, callback); + } + + protected _once( + event: TEvent, + callback: AllNodeEvents[TEvent], + ): this { + return this.once(event, callback); + } } diff --git a/packages/zwave-js/src/lib/node/NodeStatusMachine.ts b/packages/zwave-js/src/lib/node/NodeStatusMachine.ts index 9bca75019308..6823866e1295 100644 --- a/packages/zwave-js/src/lib/node/NodeStatusMachine.ts +++ b/packages/zwave-js/src/lib/node/NodeStatusMachine.ts @@ -1,6 +1,6 @@ import { type InterpreterFrom, Machine, type StateMachine } from "xstate"; -import type { ZWaveNode } from "./Node"; import { NodeStatus } from "./_Types"; +import { type NodeNetworkRole } from "./mixins/01_NetworkRole"; export interface NodeStatusStateSchema { states: { @@ -44,7 +44,9 @@ export type NodeStatusMachine = StateMachine< >; export type NodeStatusInterpreter = InterpreterFrom; -export function createNodeStatusMachine(node: ZWaveNode): NodeStatusMachine { +export function createNodeStatusMachine( + node: NodeNetworkRole, +): NodeStatusMachine { return Machine( { id: "nodeStatus", diff --git a/packages/zwave-js/src/lib/node/VirtualEndpoint.ts b/packages/zwave-js/src/lib/node/VirtualEndpoint.ts index 71223ea3d7b1..b965df0d6571 100644 --- a/packages/zwave-js/src/lib/node/VirtualEndpoint.ts +++ b/packages/zwave-js/src/lib/node/VirtualEndpoint.ts @@ -4,14 +4,16 @@ import { type CCAPIs, type CCNameOrId, PhysicalCCAPI, + type VirtualCCAPIEndpoint, getAPI, normalizeCCNameOrId, } from "@zwave-js/cc"; import { type CommandClasses, - type IVirtualEndpoint, type MulticastDestination, type SecurityClass, + type SupportsCC, + type VirtualEndpointId, ZWaveError, ZWaveErrorCodes, getCCName, @@ -29,9 +31,9 @@ import { VirtualNode } from "./VirtualNode"; * * The endpoint's capabilities are determined by the capabilities of the individual nodes' endpoints. */ -export class VirtualEndpoint implements IVirtualEndpoint { +export class VirtualEndpoint implements VirtualEndpointId, SupportsCC { public constructor( - /** The virtual node this endpoint belongs to (or undefined if it set later) */ + /** The virtual node this endpoint belongs to */ node: VirtualNode | undefined, /** The driver instance this endpoint belongs to */ protected readonly driver: Driver, @@ -49,7 +51,6 @@ export class VirtualEndpoint implements IVirtualEndpoint { public get node(): VirtualNode { return this._node; } - /** @internal */ protected setNode(node: VirtualNode): void { this._node = node; } @@ -91,7 +92,7 @@ export class VirtualEndpoint implements IVirtualEndpoint { */ public createAPI(ccId: CommandClasses): CCAPI { const createCCAPI = ( - endpoint: IVirtualEndpoint, + endpoint: VirtualCCAPIEndpoint, secClass: SecurityClass, ) => { if ( @@ -254,15 +255,4 @@ export class VirtualEndpoint implements IVirtualEndpoint { } return apiMethod.apply(CCAPI, args); } - - /** - * @internal - * DO NOT CALL THIS! - */ - public getNodeUnsafe(): never { - throw new ZWaveError( - `The node of a virtual endpoint cannot be accessed this way!`, - ZWaveErrorCodes.CC_NoNodeID, - ); - } } diff --git a/packages/zwave-js/src/lib/node/VirtualNode.ts b/packages/zwave-js/src/lib/node/VirtualNode.ts index 65cf9ce86349..0a26c4590ae2 100644 --- a/packages/zwave-js/src/lib/node/VirtualNode.ts +++ b/packages/zwave-js/src/lib/node/VirtualNode.ts @@ -10,7 +10,6 @@ import { supervisionResultToSetValueResult, } from "@zwave-js/cc/safe"; import { - type IVirtualNode, SecurityClass, SupervisionStatus, type TranslatedValueID, @@ -59,7 +58,7 @@ function groupNodesBySecurityClass( return ret; } -export class VirtualNode extends VirtualEndpoint implements IVirtualNode { +export class VirtualNode extends VirtualEndpoint { public constructor( public readonly id: number | undefined, driver: Driver, @@ -179,7 +178,7 @@ export class VirtualNode extends VirtualEndpoint implements IVirtualNode { if (api.isSetValueOptimistic(valueId)) { // If the call did not throw, assume that the call was successful and remember the new value // for each node that was affected by this command - const affectedNodes = endpointInstance.node.physicalNodes + const affectedNodes = this.physicalNodes .filter((node) => node .getEndpoint(endpointInstance.index) diff --git a/packages/zwave-js/src/lib/node/mixins/00_Base.ts b/packages/zwave-js/src/lib/node/mixins/00_Base.ts new file mode 100644 index 000000000000..fd114f1fd1c2 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/00_Base.ts @@ -0,0 +1,14 @@ +import { type NodeId } from "@zwave-js/core/safe"; +import { Endpoint } from "../Endpoint"; + +export abstract class ZWaveNodeBase extends Endpoint implements NodeId { + /** + * Whether the node should be kept awake when there are no pending messages. + */ + public keepAwake: boolean = false; + + /** The ID of this node */ + public get id(): number { + return this.nodeId; + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/01_NetworkRole.ts b/packages/zwave-js/src/lib/node/mixins/01_NetworkRole.ts new file mode 100644 index 000000000000..f43478ad7dba --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/01_NetworkRole.ts @@ -0,0 +1,144 @@ +import { + type DataRate, + type FLiRS, + type MaybeNotKnown, + NOT_KNOWN, + type NodeType, + type ProtocolVersion, + Protocols, + isLongRangeNodeId, +} from "@zwave-js/core"; +import { cacheKeys } from "../../driver/NetworkCache"; +import { ZWaveNodeBase } from "./00_Base"; + +export interface NodeNetworkRole { + /** Whether this node is always listening or not */ + readonly isListening: MaybeNotKnown; + + /** Indicates the wakeup interval if this node is a FLiRS node. `false` if it isn't. */ + readonly isFrequentListening: MaybeNotKnown; + + /** Whether this node can sleep */ + readonly canSleep: MaybeNotKnown; + + /** Whether the node supports routing/forwarding messages. */ + readonly isRouting: MaybeNotKnown; + + /** All supported data rates of this node */ + readonly supportedDataRates: MaybeNotKnown; + + /** The maximum data rate supported by this node */ + readonly maxDataRate: MaybeNotKnown; + + /** The Z-Wave protocol version this node implements */ + readonly protocolVersion: MaybeNotKnown; + + /** Whether this node is a controller (can calculate routes) or an end node (relies on route info) */ + readonly nodeType: MaybeNotKnown; + + /** + * Whether this node supports security (S0 or S2). + * **WARNING:** Nodes often report this incorrectly - do not blindly trust it. + */ + readonly supportsSecurity: MaybeNotKnown; + + /** Whether this node can issue wakeup beams to FLiRS nodes */ + readonly supportsBeaming: MaybeNotKnown; + + /** Which protocol is used to communicate with this node */ + readonly protocol: Protocols; + + /** Returns whether this node is the controller */ + readonly isControllerNode: boolean; +} + +export abstract class NetworkRoleMixin extends ZWaveNodeBase + implements NodeNetworkRole +{ + public get isListening(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).isListening); + } + protected set isListening(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).isListening, value); + } + + public get isFrequentListening(): MaybeNotKnown { + return this.driver.cacheGet( + cacheKeys.node(this.id).isFrequentListening, + ); + } + protected set isFrequentListening(value: MaybeNotKnown) { + this.driver.cacheSet( + cacheKeys.node(this.id).isFrequentListening, + value, + ); + } + + public get canSleep(): MaybeNotKnown { + // The controller node can never sleep (apparently it can report otherwise though) + if (this.isControllerNode) return false; + if (this.isListening == NOT_KNOWN) return NOT_KNOWN; + if (this.isFrequentListening == NOT_KNOWN) return NOT_KNOWN; + return !this.isListening && !this.isFrequentListening; + } + + public get isRouting(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).isRouting); + } + protected set isRouting(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).isRouting, value); + } + + public get supportedDataRates(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).supportedDataRates); + } + protected set supportedDataRates( + value: MaybeNotKnown, + ) { + this.driver.cacheSet(cacheKeys.node(this.id).supportedDataRates, value); + } + + public get maxDataRate(): MaybeNotKnown { + if (this.supportedDataRates) { + return Math.max(...this.supportedDataRates) as DataRate; + } + } + + public get protocolVersion(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).protocolVersion); + } + protected set protocolVersion(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).protocolVersion, value); + } + + public get nodeType(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).nodeType); + } + protected set nodeType(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).nodeType, value); + } + + public get supportsSecurity(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).supportsSecurity); + } + protected set supportsSecurity(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).supportsSecurity, value); + } + + public get supportsBeaming(): MaybeNotKnown { + return this.driver.cacheGet(cacheKeys.node(this.id).supportsBeaming); + } + protected set supportsBeaming(value: MaybeNotKnown) { + this.driver.cacheSet(cacheKeys.node(this.id).supportsBeaming, value); + } + + public get protocol(): Protocols { + return isLongRangeNodeId(this.id) + ? Protocols.ZWaveLongRange + : Protocols.ZWave; + } + + public get isControllerNode(): boolean { + return this.id === this.driver.controller.ownNodeId; + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/05_Security.ts b/packages/zwave-js/src/lib/node/mixins/05_Security.ts new file mode 100644 index 000000000000..326dc757449d --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/05_Security.ts @@ -0,0 +1,80 @@ +import { + CacheBackedMap, + type CommandClasses, + type MaybeNotKnown, + NOT_KNOWN, + type QuerySecurityClasses, + SecurityClass, + type SetSecurityClass, + securityClassOrder, +} from "@zwave-js/core"; +import { getEnumMemberName } from "@zwave-js/shared"; +import { type Driver } from "../../driver/Driver"; +import { cacheKeys } from "../../driver/NetworkCache"; +import { type DeviceClass } from "../DeviceClass"; +import { NetworkRoleMixin } from "./01_NetworkRole"; + +export abstract class NodeSecurityMixin extends NetworkRoleMixin + implements QuerySecurityClasses, SetSecurityClass +{ + public constructor( + nodeId: number, + driver: Driver, + index: number, + deviceClass?: DeviceClass, + supportedCCs?: CommandClasses[], + ) { + super(nodeId, driver, index, deviceClass, supportedCCs); + + this.securityClasses = new CacheBackedMap(this.driver.networkCache, { + prefix: cacheKeys.node(this.id)._securityClassBaseKey + ".", + suffixSerializer: (value: SecurityClass) => + getEnumMemberName(SecurityClass, value), + suffixDeserializer: (key: string) => { + if ( + key in SecurityClass + && typeof (SecurityClass as any)[key] === "number" + ) { + return (SecurityClass as any)[key]; + } + }, + }); + } + + /** @internal */ + // This a CacheBackedMap that's assigned in the constructor + public readonly securityClasses: Map; + + public get isSecure(): MaybeNotKnown { + const securityClass = this.getHighestSecurityClass(); + if (securityClass == undefined) return NOT_KNOWN; + if (securityClass === SecurityClass.None) return false; + return true; + } + + public hasSecurityClass( + securityClass: SecurityClass, + ): MaybeNotKnown { + return this.securityClasses.get(securityClass); + } + + public setSecurityClass( + securityClass: SecurityClass, + granted: boolean, + ): void { + this.securityClasses.set(securityClass, granted); + } + + public getHighestSecurityClass(): MaybeNotKnown { + if (this.securityClasses.size === 0) return undefined; + let missingSome = false; + for (const secClass of securityClassOrder) { + if (this.securityClasses.get(secClass) === true) return secClass; + if (!this.securityClasses.has(secClass)) { + missingSome = true; + } + } + // If we don't have the info for every security class, we don't know the highest one yet + return missingSome ? NOT_KNOWN : SecurityClass.None; + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/10_Events.ts b/packages/zwave-js/src/lib/node/mixins/10_Events.ts new file mode 100644 index 000000000000..92909e3713e9 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/10_Events.ts @@ -0,0 +1,41 @@ +import { type EventHandler } from "@zwave-js/shared"; +import { type StatisticsEventCallbacksWithSelf } from "../../driver/Statistics"; +import { type ZWaveNode } from "../Node"; +import { type NodeStatistics } from "../NodeStatistics"; +import { type ZWaveNodeEventCallbacks } from "../_Types"; +import { NodeSecurityMixin } from "./05_Security"; + +// This mixin is a slightly ugly workaround to allow other mixins to +// interact with events which would normally take an instance of ZWaveNode + +type ReplaceNodeWithThis = { + [K in keyof T]: T[K] extends ZWaveNode ? TThis : T[K]; +}; + +export type EventsToAbstract> = { + [K in keyof T]: ( + ...args: ReplaceNodeWithThis> + ) => void; +}; + +type AbstractNodeEvents = EventsToAbstract< + TThis, + & ZWaveNodeEventCallbacks + & StatisticsEventCallbacksWithSelf +>; + +export abstract class NodeEventsMixin extends NodeSecurityMixin { + protected abstract _emit>( + event: TEvent, + ...args: Parameters[TEvent]> + ): boolean; + + protected abstract _on>( + event: TEvent, + callback: AbstractNodeEvents[TEvent], + ): this; + protected abstract _once>( + event: TEvent, + callback: AbstractNodeEvents[TEvent], + ): this; +} diff --git a/packages/zwave-js/src/lib/node/mixins/20_Status.ts b/packages/zwave-js/src/lib/node/mixins/20_Status.ts new file mode 100644 index 000000000000..7a4e08693af9 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/20_Status.ts @@ -0,0 +1,196 @@ +import { type CommandClasses, InterviewStage } from "@zwave-js/core"; +import { type Driver } from "../../driver/Driver"; +import { cacheKeys } from "../../driver/NetworkCache"; +import { type Extended, interpretEx } from "../../driver/StateMachineShared"; +import { type DeviceClass } from "../DeviceClass"; +import { + type NodeReadyInterpreter, + createNodeReadyMachine, +} from "../NodeReadyMachine"; +import { + type NodeStatusInterpreter, + createNodeStatusMachine, + nodeStatusMachineStateToNodeStatus, +} from "../NodeStatusMachine"; +import { NodeStatus } from "../_Types"; +import { NodeEventsMixin } from "./10_Events"; + +export interface NodeWithStatus { + /** + * Which status the node is believed to be in + */ + status: NodeStatus; + + /** + * Whether the node is ready to be used + */ + ready: boolean; + + /** + * @internal + * Marks this node as dead (if applicable) + */ + markAsDead(): void; + + /** + * @internal + * Marks this node as alive (if applicable) + */ + markAsAlive(): void; + + /** + * @internal + * Marks this node as asleep (if applicable) + */ + markAsAsleep(): void; + + /** + * @internal + * Marks this node as awake (if applicable) + */ + markAsAwake(): void; + + /** + * Which interview stage was last completed + */ + interviewStage: InterviewStage; +} + +export abstract class NodeStatusMixin extends NodeEventsMixin + implements NodeWithStatus +{ + public constructor( + nodeId: number, + driver: Driver, + index: number, + deviceClass?: DeviceClass, + supportedCCs?: CommandClasses[], + ) { + super(nodeId, driver, index, deviceClass, supportedCCs); + + // Create and hook up the status machine + this.statusMachine = interpretEx( + createNodeStatusMachine(this), + ); + this.statusMachine.onTransition((state) => { + if (state.changed) { + this.onStatusChange( + nodeStatusMachineStateToNodeStatus(state.value as any), + ); + } + }); + this.statusMachine.start(); + + this.readyMachine = interpretEx(createNodeReadyMachine()); + this.readyMachine.onTransition((state) => { + if (state.changed) { + this.onReadyChange(state.value === "ready"); + } + }); + this.readyMachine.start(); + } + + protected statusMachine: Extended; + + private _status: NodeStatus = NodeStatus.Unknown; + + /** + * Which status the node is believed to be in + */ + public get status(): NodeStatus { + return this._status; + } + + private onStatusChange(newStatus: NodeStatus) { + // Ignore duplicate events + if (newStatus === this._status) return; + + const oldStatus = this._status; + this._status = newStatus; + if (this._status === NodeStatus.Asleep) { + this._emit("sleep", this, oldStatus); + } else if (this._status === NodeStatus.Awake) { + this._emit("wake up", this, oldStatus); + } else if (this._status === NodeStatus.Dead) { + this._emit("dead", this, oldStatus); + } else if (this._status === NodeStatus.Alive) { + this._emit("alive", this, oldStatus); + } + + // To be marked ready, a node must be known to be not dead. + // This means that listening nodes must have communicated with us and + // sleeping nodes are assumed to be ready + this.readyMachine.send( + this._status !== NodeStatus.Unknown + && this._status !== NodeStatus.Dead + ? "NOT_DEAD" + : "MAYBE_DEAD", + ); + } + + /** + * @internal + * Marks this node as dead (if applicable) + */ + public markAsDead(): void { + this.statusMachine.send("DEAD"); + } + + /** + * @internal + * Marks this node as alive (if applicable) + */ + public markAsAlive(): void { + this.statusMachine.send("ALIVE"); + } + + /** + * @internal + * Marks this node as asleep (if applicable) + */ + public markAsAsleep(): void { + this.statusMachine.send("ASLEEP"); + } + + /** + * @internal + * Marks this node as awake (if applicable) + */ + public markAsAwake(): void { + this.statusMachine.send("AWAKE"); + } + + // The node is only ready when the interview has been completed + // to a certain degree + + protected readyMachine: Extended; + private _ready: boolean = false; + + private onReadyChange(ready: boolean) { + // Ignore duplicate events + if (ready === this._ready) return; + + this._ready = ready; + if (ready) this._emit("ready", this); + } + + /** + * Whether the node is ready to be used + */ + public get ready(): boolean { + return this._ready; + } + protected set ready(ready: boolean) { + this._ready = ready; + } + + public get interviewStage(): InterviewStage { + return ( + this.driver.cacheGet(cacheKeys.node(this.id).interviewStage) + ?? InterviewStage.None + ); + } + public set interviewStage(value: InterviewStage) { + this.driver.cacheSet(cacheKeys.node(this.id).interviewStage, value); + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/30_Wakeup.ts b/packages/zwave-js/src/lib/node/mixins/30_Wakeup.ts new file mode 100644 index 000000000000..1c2df3059705 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/30_Wakeup.ts @@ -0,0 +1,76 @@ +import { + CommandClasses, + InterviewStage, + NodeStatus, + ZWaveError, + ZWaveErrorCodes, +} from "@zwave-js/core"; +import { NodeStatusMixin } from "./20_Status"; + +/** + * Interface for NodeWakeupMixin + */ +export interface NodeWakeup { + /** Returns a promise that resolves when the node wakes up the next time or immediately if the node is already awake. */ + waitForWakeup(): Promise; + + /** + * Sends the node a WakeUpCCNoMoreInformation so it can go back to sleep + * @internal + */ + sendNoMoreInformation(): Promise; +} + +export abstract class NodeWakeupMixin extends NodeStatusMixin + implements NodeWakeup +{ + public waitForWakeup(): Promise { + if (!this.canSleep || !this.supportsCC(CommandClasses["Wake Up"])) { + throw new ZWaveError( + `Node ${this.id} does not support wakeup!`, + ZWaveErrorCodes.CC_NotSupported, + ); + } else if (this.status === NodeStatus.Awake) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + this._once("wake up", () => resolve()); + }); + } + + private isSendingNoMoreInformation: boolean = false; + public async sendNoMoreInformation(): Promise { + // Don't send the node back to sleep if it should be kept awake + if (this.keepAwake) return false; + + // Avoid calling this method more than once + if (this.isSendingNoMoreInformation) return false; + this.isSendingNoMoreInformation = true; + + let msgSent = false; + if ( + this.status === NodeStatus.Awake + && this.interviewStage === InterviewStage.Complete + ) { + this.driver.controllerLog.logNode(this.id, { + message: "Sending node back to sleep...", + direction: "outbound", + }); + try { + // it is important that we catch errors in this call + // otherwise, this method will not work anymore because + // isSendingNoMoreInformation is stuck on `true` + await this.commandClasses["Wake Up"].sendNoMoreInformation(); + msgSent = true; + } catch { + /* ignore */ + } finally { + this.markAsAsleep(); + } + } + + this.isSendingNoMoreInformation = false; + return msgSent; + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/40_Values.ts b/packages/zwave-js/src/lib/node/mixins/40_Values.ts new file mode 100644 index 000000000000..0171d8525aca --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/40_Values.ts @@ -0,0 +1,246 @@ +import { + type CCValueOptions, + CommandClass, + defaultCCValueOptions, + getCCValues, +} from "@zwave-js/cc"; +import { + type CommandClasses, + type MaybeNotKnown, + type MetadataUpdatedArgs, + ValueDB, + type ValueID, + ValueMetadata, + type ValueUpdatedArgs, + applicationCCs, + getCCName, +} from "@zwave-js/core"; +import { pick } from "@zwave-js/shared"; +import { type Driver } from "../../driver/Driver"; +import { type DeviceClass } from "../DeviceClass"; +import { type ZWaveNodeValueEventCallbacks } from "../_Types"; +import * as nodeUtils from "../utils"; +import { NodeWakeupMixin } from "./30_Wakeup"; + +/** Defines functionality of Z-Wave nodes related to the value DB */ +export interface NodeValues { + /** + * Provides access to this node's values + * @internal + */ + readonly valueDB: ValueDB; + + /** + * Retrieves a stored value for a given value id. + * This does not request an updated value from the node! + */ + getValue(valueId: ValueID): MaybeNotKnown; + + /** + * Returns when the given value id was last updated by an update from the node. + */ + getValueTimestamp(valueId: ValueID): MaybeNotKnown; + + /** + * Retrieves metadata for a given value id. + * This can be used to enhance the user interface of an application + */ + getValueMetadata(valueId: ValueID): ValueMetadata; +} + +export abstract class NodeValuesMixin extends NodeWakeupMixin + implements NodeValues +{ + public constructor( + nodeId: number, + driver: Driver, + endpointIndex: number, + deviceClass?: DeviceClass, + supportedCCs?: CommandClasses[], + valueDB?: ValueDB, + ) { + // Define this node's intrinsic endpoint as the root device (0) + super(nodeId, driver, endpointIndex, deviceClass, supportedCCs); + this._valueDB = valueDB + ?? new ValueDB(nodeId, driver.valueDB!, driver.metadataDB!); + + // Pass value events to our listeners + for ( + const event of [ + "value added", + "value updated", + "value removed", + "value notification", + "metadata updated", + ] as const + ) { + this._valueDB.on(event, this.translateValueEvent.bind(this, event)); + } + } + + protected _valueDB: ValueDB; + public get valueDB(): ValueDB { + return this._valueDB; + } + + public getValue(valueId: ValueID): MaybeNotKnown { + return this._valueDB.getValue(valueId); + } + + public getValueTimestamp(valueId: ValueID): MaybeNotKnown { + return this._valueDB.getTimestamp(valueId); + } + + public getValueMetadata(valueId: ValueID): ValueMetadata { + // Check if a corresponding CC value is defined for this value ID + // so we can extend the returned metadata + const definedCCValues = getCCValues(valueId.commandClass); + let valueOptions: Required | undefined; + let meta: ValueMetadata | undefined; + if (definedCCValues) { + const value = Object.values(definedCCValues) + .find((v) => v?.is(valueId)); + if (value) { + if (typeof value !== "function") { + meta = value.meta; + } + valueOptions = value.options; + } + } + + const existingMetadata = this._valueDB.getMetadata(valueId); + return { + // The priority for returned metadata is valueDB > defined value > Any (default) + ...(existingMetadata ?? meta ?? ValueMetadata.Any), + // ...except for these flags, which are taken from defined values: + stateful: valueOptions?.stateful ?? defaultCCValueOptions.stateful, + secret: valueOptions?.secret ?? defaultCCValueOptions.secret, + }; + } + + /** + * Enhances the raw event args of the ValueDB so it can be consumed better by applications + */ + protected translateValueEvent( + eventName: keyof ZWaveNodeValueEventCallbacks, + arg: T, + ): void { + // Try to retrieve the speaking CC name + const outArg = nodeUtils.translateValueID(this.driver, this, arg); + // This can happen for value updated events + if ("source" in outArg) delete outArg.source; + + const loglevel = this.driver.getLogConfig().level; + + // If this is a metadata event, make sure we return the merged metadata + if ("metadata" in outArg) { + (outArg as unknown as MetadataUpdatedArgs).metadata = this + .getValueMetadata(arg); + } + + const ccInstance = CommandClass.createInstanceUnchecked( + this, + arg.commandClass, + ); + const isInternalValue = !!ccInstance?.isInternalValue(arg); + // Check whether this value change may be logged + const isSecretValue = !!ccInstance?.isSecretValue(arg); + + if (loglevel === "silly") { + this.driver.controllerLog.logNode(this.id, { + message: `[translateValueEvent: ${eventName}] + commandClass: ${getCCName(arg.commandClass)} + endpoint: ${arg.endpoint} + property: ${arg.property} + propertyKey: ${arg.propertyKey} + internal: ${isInternalValue} + secret: ${isSecretValue} + event source: ${(arg as any as ValueUpdatedArgs).source}`, + level: "silly", + }); + } + + if ( + !isSecretValue + && (arg as any as ValueUpdatedArgs).source !== "driver" + ) { + // Log the value change, except for updates caused by the driver itself + // I don't like the splitting and any but its the easiest solution here + const [changeTarget, changeType] = eventName.split(" "); + const logArgument = { + ...outArg, + nodeId: this.id, + internal: isInternalValue, + }; + if (changeTarget === "value") { + this.driver.controllerLog.value( + changeType as any, + logArgument as any, + ); + } else if (changeTarget === "metadata") { + this.driver.controllerLog.metadataUpdated(logArgument); + } + } + + // Don't expose value events for internal value IDs... + if (isInternalValue) return; + + if (loglevel === "silly") { + this.driver.controllerLog.logNode(this.id, { + message: `[translateValueEvent: ${eventName}] + is root endpoint: ${!arg.endpoint} + is application CC: ${applicationCCs.includes(arg.commandClass)} + should hide root values: ${ + nodeUtils.shouldHideRootApplicationCCValues( + this.driver, + this.id, + ) + }`, + level: "silly", + }); + } + + // ... and root values ID that mirrors endpoint functionality + if ( + // Only root endpoint values need to be filtered + !arg.endpoint + // Only application CCs need to be filtered + && applicationCCs.includes(arg.commandClass) + // and only if the endpoints are not unnecessary and the root values mirror them + && nodeUtils.shouldHideRootApplicationCCValues(this.driver, this.id) + ) { + // Iterate through all possible non-root endpoints of this node and + // check if there is a value ID that mirrors root endpoint functionality + for ( + const endpoint of nodeUtils.getEndpointIndizes( + this.driver, + this.id, + ) + ) { + const possiblyMirroredValueID: ValueID = { + // same CC, property and key + ...pick(arg, ["commandClass", "property", "propertyKey"]), + // but different endpoint + endpoint, + }; + if (this.valueDB.hasValue(possiblyMirroredValueID)) { + if (loglevel === "silly") { + this.driver.controllerLog.logNode(this.id, { + message: + `[translateValueEvent: ${eventName}] found mirrored value ID on different endpoint, ignoring event: + commandClass: ${getCCName(possiblyMirroredValueID.commandClass)} + endpoint: ${possiblyMirroredValueID.endpoint} + property: ${possiblyMirroredValueID.property} + propertyKey: ${possiblyMirroredValueID.propertyKey}`, + level: "silly", + }); + } + + return; + } + } + } + // And pass the translated event to our listeners + this._emit(eventName, this, outArg as any); + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/50_Endpoints.ts b/packages/zwave-js/src/lib/node/mixins/50_Endpoints.ts new file mode 100644 index 000000000000..1e0985293f26 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/50_Endpoints.ts @@ -0,0 +1,183 @@ +import { MultiChannelCCValues } from "@zwave-js/cc"; +import { + type CommandClasses, + type GetAllEndpoints, + type GetEndpoint, + type MaybeNotKnown, + ZWaveError, + ZWaveErrorCodes, +} from "@zwave-js/core"; +import { isArray, isObject } from "alcalzone-shared/typeguards"; +import { DeviceClass } from "../DeviceClass"; +import { Endpoint } from "../Endpoint"; +import * as nodeUtils from "../utils"; +import { NodeValuesMixin } from "./40_Values"; + +/** Defines functionality of Z-Wave nodes related to accessing endpoints and their capabilities */ +export interface Endpoints { + /** Whether the endpoint count is dynamic */ + readonly endpointCountIsDynamic: MaybeNotKnown; + /** Whether all endpoints have identical capabilities */ + readonly endpointsHaveIdenticalCapabilities: MaybeNotKnown; + /** The number of individual endpoints */ + readonly individualEndpointCount: MaybeNotKnown; + /** The number of aggregated endpoints */ + readonly aggregatedEndpointCount: MaybeNotKnown; + /** Returns the current endpoint count of this node. + * + * If you want to enumerate the existing endpoints, use `getEndpointIndizes` instead. + * Some devices are known to contradict themselves. + */ + getEndpointCount(): number; + /** Returns indizes of all endpoints on the node. */ + getEndpointIndizes(): number[]; + /** Returns an endpoint of this node with the given index. 0 returns the node itself. */ + getEndpoint(index: 0): Endpoint; + getEndpoint(index: number): Endpoint | undefined; + /** Returns an endpoint of this node with the given index. Throws if the endpoint does not exist. */ + getEndpointOrThrow(index: number): Endpoint; + /** Returns a list of all endpoints of this node, including the root endpoint (index 0) */ + getAllEndpoints(): Endpoint[]; +} + +export abstract class EndpointsMixin extends NodeValuesMixin + implements Endpoints, GetEndpoint, GetAllEndpoints +{ + public get endpointCountIsDynamic(): MaybeNotKnown { + return nodeUtils.endpointCountIsDynamic(this.driver, this.id); + } + + public get endpointsHaveIdenticalCapabilities(): MaybeNotKnown { + return nodeUtils.endpointsHaveIdenticalCapabilities( + this.driver, + this.id, + ); + } + + public get individualEndpointCount(): MaybeNotKnown { + return nodeUtils.getIndividualEndpointCount(this.driver, this.id); + } + + public get aggregatedEndpointCount(): MaybeNotKnown { + return nodeUtils.getAggregatedEndpointCount(this.driver, this.id); + } + + /** Returns the device class of an endpoint. Falls back to the node's device class if the information is not known. */ + private getEndpointDeviceClass(index: number): MaybeNotKnown { + const deviceClass = this.getValue<{ + generic: number; + specific: number; + }>( + MultiChannelCCValues.endpointDeviceClass.endpoint( + this.endpointsHaveIdenticalCapabilities ? 1 : index, + ), + ); + if (deviceClass && this.deviceClass) { + return new DeviceClass( + this.deviceClass.basic, + deviceClass.generic, + deviceClass.specific, + ); + } + // fall back to the node's device class if it is known + return this.deviceClass; + } + + private getEndpointCCs(index: number): MaybeNotKnown { + const ret = this.getValue( + MultiChannelCCValues.endpointCCs.endpoint( + this.endpointsHaveIdenticalCapabilities ? 1 : index, + ), + ); + // Workaround for the change in #1977 + if (isArray(ret)) { + // The value is set up correctly, return it + return ret as CommandClasses[]; + } else if (isObject(ret) && "supportedCCs" in ret) { + return ret.supportedCCs as CommandClasses[]; + } + } + + /** + * Returns the current endpoint count of this node. + * + * If you want to enumerate the existing endpoints, use `getEndpointIndizes` instead. + * Some devices are known to contradict themselves. + */ + public getEndpointCount(): number { + return nodeUtils.getEndpointCount(this.driver, this.id); + } + + /** + * Returns indizes of all endpoints on the node. + */ + public getEndpointIndizes(): number[] { + return nodeUtils.getEndpointIndizes(this.driver, this.id); + } + + /** Whether the Multi Channel CC has been interviewed and all endpoint information is known */ + private get isMultiChannelInterviewComplete(): boolean { + return nodeUtils.isMultiChannelInterviewComplete(this.driver, this.id); + } + + /** Cache for this node's endpoint instances */ + protected _endpointInstances = new Map(); + /** + * Returns an endpoint of this node with the given index. 0 returns the node itself. + */ + public getEndpoint(index: 0): Endpoint; + public getEndpoint(index: number): Endpoint | undefined; + public getEndpoint(index: number): Endpoint | undefined { + if (index < 0) { + throw new ZWaveError( + "The endpoint index must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + // Zero is the root endpoint - i.e. this node + if (index === 0) return this; + // Check if the Multi Channel CC interview for this node is completed, + // because we don't have all the information before that + if (!this.isMultiChannelInterviewComplete) { + this.driver.driverLog.print( + `Node ${this.id}, Endpoint ${index}: Trying to access endpoint instance before Multi Channel interview`, + "error", + ); + return undefined; + } + // Check if the endpoint index is in the list of known endpoint indizes + if (!this.getEndpointIndizes().includes(index)) return undefined; + + // Create an endpoint instance if it does not exist + if (!this._endpointInstances.has(index)) { + this._endpointInstances.set( + index, + new Endpoint( + this.id, + this.driver, + index, + this.getEndpointDeviceClass(index), + this.getEndpointCCs(index), + ), + ); + } + return this._endpointInstances.get(index)!; + } + + public getEndpointOrThrow(index: number): Endpoint { + const ret = this.getEndpoint(index); + if (!ret) { + throw new ZWaveError( + `Endpoint ${index} does not exist on Node ${this.id}`, + ZWaveErrorCodes.Controller_EndpointNotFound, + ); + } + return ret; + } + + /** Returns a list of all endpoints of this node, including the root endpoint (index 0) */ + public getAllEndpoints(): Endpoint[] { + // FIXME: GH#7261 we should not need to cast here + return nodeUtils.getAllEndpoints(this.driver, this); + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts b/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts new file mode 100644 index 000000000000..32166745a973 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts @@ -0,0 +1,189 @@ +import { type CCAPI } from "@zwave-js/cc"; +import { type ValueDB, normalizeValueID } from "@zwave-js/core"; +import { + type CommandClasses, + MessagePriority, + type ValueID, + type ValueRemovedArgs, + type ValueUpdatedArgs, +} from "@zwave-js/core/safe"; +import { type NodeSchedulePollOptions } from "@zwave-js/host"; +import { ObjectKeyMap } from "@zwave-js/shared"; +import { isDeepStrictEqual } from "node:util"; +import { type Driver } from "../../driver/Driver"; +import { type DeviceClass } from "../DeviceClass"; +import { EndpointsMixin } from "./50_Endpoints"; + +export interface ScheduledPoll { + timeout: NodeJS.Timeout; + expectedValue?: unknown; +} + +/** Defines functionality of Z-Wave nodes for scheduling polls for a later time and canceling scheduled polls */ +export interface SchedulePoll { + /** + * @internal + * Returns whether a poll is currently scheduled for this node + */ + hasScheduledPolls(): boolean; + + /** + * @internal + * Schedules a value to be polled after a given time. Only one schedule can be active for a given value ID. + * @returns `true` if the poll was scheduled, `false` otherwise + */ + schedulePoll( + valueId: ValueID, + options: NodeSchedulePollOptions, + ): boolean; + + /** + * @internal + * Cancels a poll that has been scheduled with schedulePoll. + * + * @param actualValue If given, this indicates the value that was received by a node, which triggered the poll to be canceled. + * If the scheduled poll expects a certain value and this matches the expected value for the scheduled poll, the poll will be canceled. + */ + cancelScheduledPoll( + valueId: ValueID, + actualValue?: unknown, + ): boolean; + + /** + * @internal + * Cancels all polls that have been scheduled with schedulePoll. + */ + cancelAllScheduledPolls(): void; +} + +export abstract class SchedulePollMixin extends EndpointsMixin + implements SchedulePoll +{ + public constructor( + nodeId: number, + driver: Driver, + endpointIndex: number, + deviceClass?: DeviceClass, + supportedCCs?: CommandClasses[], + valueDB?: ValueDB, + ) { + super( + nodeId, + driver, + endpointIndex, + deviceClass, + supportedCCs, + valueDB, + ); + + // Avoid verifying a value change for which we recently received an update + for (const event of ["value updated", "value removed"] as const) { + this.valueDB.on( + event, + (args: ValueUpdatedArgs | ValueRemovedArgs) => { + // Value updates caused by the driver should never cancel a scheduled poll + if ("source" in args && args.source === "driver") return; + + if ( + this.cancelScheduledPoll( + args, + (args as ValueUpdatedArgs).newValue, + ) + ) { + this.driver.controllerLog.logNode( + this.id, + "Scheduled poll canceled because expected value was received", + "verbose", + ); + } + }, + ); + } + } + + /** + * All polls that are currently scheduled for this node + */ + private _scheduledPolls = new ObjectKeyMap(); + + public hasScheduledPolls(): boolean { + return this._scheduledPolls.size > 0; + } + + public schedulePoll( + valueId: ValueID, + options: NodeSchedulePollOptions = {}, + ): boolean { + const { + timeoutMs = this.driver.options.timeouts.refreshValue, + expectedValue, + } = options; + + // Avoid false positives or false negatives due to a mis-formatted value ID + valueId = normalizeValueID(valueId); + + // Try to retrieve the corresponding CC API + const endpointInstance = this.getEndpoint(valueId.endpoint || 0); + if (!endpointInstance) return false; + + const api = ( + (endpointInstance.commandClasses as any)[ + valueId.commandClass + ] as CCAPI + ).withOptions({ + // We do not want to delay more important communication by polling, so give it + // the lowest priority and don't retry unless overwritten by the options + maxSendAttempts: 1, + priority: MessagePriority.Poll, + }); + + // Check if the pollValue method is implemented + if (!api.pollValue) return false; + + // make sure there is only one timeout instance per poll + this.cancelScheduledPoll(valueId); + const timeout = setTimeout(async () => { + // clean up after the timeout + this.cancelScheduledPoll(valueId); + try { + await api.pollValue!.call(api, valueId); + } catch { + /* ignore */ + } + }, timeoutMs).unref(); + this._scheduledPolls.set(valueId, { timeout, expectedValue }); + + return true; + } + + public cancelScheduledPoll( + valueId: ValueID, + actualValue?: unknown, + ): boolean { + // Avoid false positives or false negatives due to a mis-formatted value ID + valueId = normalizeValueID(valueId); + + const poll = this._scheduledPolls.get(valueId); + if (!poll) return false; + + if ( + actualValue !== undefined + && poll.expectedValue !== undefined + && !isDeepStrictEqual(poll.expectedValue, actualValue) + ) { + return false; + } + + clearTimeout(poll.timeout); + this._scheduledPolls.delete(valueId); + + return true; + } + + public cancelAllScheduledPolls(): void { + // Remove queued polls that would interfere with the interview + for (const valueId of this._scheduledPolls.keys()) { + this.cancelScheduledPoll(valueId); + } + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts new file mode 100644 index 000000000000..8214c9756b92 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts @@ -0,0 +1,993 @@ +import { + type FirmwareUpdateMetaData, + FirmwareUpdateMetaDataCC, + FirmwareUpdateMetaDataCCGet, + type FirmwareUpdateMetaDataCCMetaDataGet, + FirmwareUpdateMetaDataCCReport, + FirmwareUpdateMetaDataCCRequestReport, + FirmwareUpdateMetaDataCCStatusReport, + type FirmwareUpdateOptions, + type FirmwareUpdateProgress, + FirmwareUpdateRequestStatus, + type FirmwareUpdateResult, + FirmwareUpdateStatus, + getEffectiveCCVersion, + isCommandClassContainer, +} from "@zwave-js/cc"; +import { + CRC16_CCITT, + CommandClasses, + EncapsulationFlags, + type Firmware, + SecurityClass, + ZWaveError, + ZWaveErrorCodes, + securityClassIsS2, + timespan, +} from "@zwave-js/core"; +import { getEnumMemberName, throttle } from "@zwave-js/shared"; +import { distinct } from "alcalzone-shared/arrays"; +import { wait } from "alcalzone-shared/async"; +import { + type DeferredPromise, + createDeferredPromise, +} from "alcalzone-shared/deferred-promise"; +import { roundTo } from "alcalzone-shared/math"; +import { randomBytes } from "node:crypto"; +import { type Task, type TaskBuilder, TaskPriority } from "../../driver/Task"; +import { type Transaction } from "../../driver/Transaction"; +import { SchedulePollMixin } from "./60_ScheduledPoll"; + +interface AbortFirmwareUpdateContext { + abort: boolean; + tooLateToAbort: boolean; + abortPromise: DeferredPromise; +} + +type PartialFirmwareUpdateResult = + & Pick + & { success: boolean }; + +/** Checks if a task belongs to a route rebuilding process */ +export function isFirmwareUpdateOTATask(t: Task): boolean { + return t.tag?.id === "firmware-update-ota"; +} + +export interface NodeFirmwareUpdate { + /** + * Aborts an active firmware update process + */ + abortFirmwareUpdate(): Promise; + + /** + * Performs an OTA firmware upgrade of one or more chips on this node. + * + * This method will resolve after the process has **COMPLETED**. Failure to start any one of the provided updates will throw an error. + * + * **WARNING: Use at your own risk! We don't take any responsibility if your devices don't work after an update.** + * + * @param updates An array of firmware updates that will be done in sequence + * + * @returns Whether all of the given updates were successful. + */ + updateFirmware( + updates: Firmware[], + options?: FirmwareUpdateOptions, + ): Promise; + + /** + * Returns whether a firmware update is in progress for this node. + */ + isFirmwareUpdateInProgress(): boolean; +} + +export abstract class FirmwareUpdateMixin extends SchedulePollMixin + implements NodeFirmwareUpdate +{ + private _abortFirmwareUpdate: (() => Promise) | undefined; + public async abortFirmwareUpdate(): Promise { + if (!this._abortFirmwareUpdate) return; + await this._abortFirmwareUpdate(); + } + + // Stores the CRC of the previously transferred firmware image. + // Allows detecting whether resuming is supported and where to continue in a multi-file transfer. + private _previousFirmwareCRC: number | undefined; + + /** Is used to remember fragment requests that came in before they were able to be handled */ + private _firmwareUpdatePrematureRequest: + | FirmwareUpdateMetaDataCCGet + | undefined; + + public async updateFirmware( + updates: Firmware[], + options: FirmwareUpdateOptions = {}, + ): Promise { + if (updates.length === 0) { + throw new ZWaveError( + `At least one update must be provided`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + // Check that each update has a buffer with at least 1 byte + if (updates.some((u) => u.data.length === 0)) { + throw new ZWaveError( + `All firmware updates must have a non-empty data buffer`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + // Check that the targets are not duplicates + if ( + distinct(updates.map((u) => u.firmwareTarget ?? 0)).length + !== updates.length + ) { + throw new ZWaveError( + `The target of all provided firmware updates must be unique`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + // Don't start the process twice + if (this.driver.controller.isFirmwareUpdateInProgress()) { + throw new ZWaveError( + `Failed to start the update: An OTW upgrade of the controller is in progress!`, + ZWaveErrorCodes.FirmwareUpdateCC_Busy, + ); + } + + // Don't allow starting two firmware updates for the same node + const task = this.getUpdateFirmwareTask(updates, options); + if (task instanceof Promise) { + throw new ZWaveError( + `Failed to start the update: A firmware update is already in progress for this node!`, + ZWaveErrorCodes.FirmwareUpdateCC_Busy, + ); + } + + // Queue the task + return this.driver.scheduler.queueTask(task); + } + + public isFirmwareUpdateInProgress(): boolean { + return !!this.driver.scheduler.findTask(isFirmwareUpdateOTATask); + } + + private getUpdateFirmwareTask( + updates: Firmware[], + options: FirmwareUpdateOptions = {}, + ): Promise | TaskBuilder { + const self = this; + + // This task should only run once at a time + const existingTask = this.driver.scheduler.findTask< + FirmwareUpdateResult + >((t) => + t.tag?.id === "firmware-update-ota" + && t.tag.nodeId === self.id + ); + if (existingTask) return existingTask; + + let keepAwake: boolean; + + return { + // Firmware updates cause a lot of traffic. Execute them in the background. + priority: TaskPriority.Lower, + tag: { id: "firmware-update-ota", nodeId: self.id }, + task: async function* firmwareUpdateTask() { + // Keep battery powered nodes awake during the process + keepAwake = self.keepAwake; + self.keepAwake = true; + + // Support aborting the update + const abortContext = { + abort: false, + tooLateToAbort: false, + abortPromise: createDeferredPromise(), + }; + + self._abortFirmwareUpdate = async () => { + if (abortContext.tooLateToAbort) { + throw new ZWaveError( + `The firmware update was transmitted completely, cannot abort anymore.`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort, + ); + } + + self.driver.controllerLog.logNode(self.id, { + message: `Aborting firmware update...`, + direction: "outbound", + }); + + // Trigger the abort + abortContext.abort = true; + const aborted = await abortContext.abortPromise; + if (!aborted) { + throw new ZWaveError( + `The node did not acknowledge the aborted update`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort, + ); + } + self.driver.controllerLog.logNode(self.id, { + message: `Firmware update aborted`, + direction: "inbound", + }); + }; + + // Prepare the firmware update + let fragmentSizeSecure: number; + let fragmentSizeNonSecure: number; + let meta: FirmwareUpdateMetaData; + try { + const prepareResult = await self + .prepareFirmwareUpdateInternal( + updates.map((u) => u.firmwareTarget ?? 0), + abortContext, + ); + + // Handle early aborts + if (abortContext.abort) { + const result: FirmwareUpdateResult = { + success: false, + status: FirmwareUpdateStatus + .Error_TransmissionFailed, + reInterview: false, + }; + self._emit( + "firmware update finished", + self, + result, + ); + return result; + } + + // If the firmware update was not aborted, prepareResult is definitely defined + ({ + fragmentSizeSecure, + fragmentSizeNonSecure, + ...meta + } = prepareResult!); + } catch { + // Not sure what the error is, but we'll label it "transmission failed" + const result: FirmwareUpdateResult = { + success: false, + status: FirmwareUpdateStatus.Error_TransmissionFailed, + reInterview: false, + }; + + return result; + } + + yield; // Give the task scheduler time to do something else + + // The resume and non-secure transfer features may not be supported by the node + // If not, disable them, even though the application requested them + if (!meta.supportsResuming) options.resume = false; + + const securityClass = self.getHighestSecurityClass(); + const isSecure = securityClass === SecurityClass.S0_Legacy + || securityClassIsS2(securityClass); + if (!isSecure) { + // The nonSecureTransfer option is only relevant for secure devices + options.nonSecureTransfer = false; + } else if (!meta.supportsNonSecureTransfer) { + options.nonSecureTransfer = false; + } + + // Throttle the progress emitter so applications can handle the load of events + const notifyProgress = throttle( + (progress) => + self._emit( + "firmware update progress", + self, + progress, + ), + 250, + true, + ); + + // If resuming is supported and desired, try to figure out with which file to continue + const updatesWithChecksum = updates.map((u) => ({ + ...u, + checksum: CRC16_CCITT(u.data), + })); + let skipFinishedFiles = -1; + let shouldResume = options.resume + && self._previousFirmwareCRC != undefined; + if (shouldResume) { + skipFinishedFiles = updatesWithChecksum.findIndex( + (u) => u.checksum === self._previousFirmwareCRC, + ); + if (skipFinishedFiles === -1) shouldResume = false; + } + + // Perform all firmware updates in sequence + let updateResult!: PartialFirmwareUpdateResult; + let conservativeWaitTime: number; + + const totalBytes: number = updatesWithChecksum.reduce( + (total, update) => total + update.data.length, + 0, + ); + let sentBytesOfPreviousFiles = 0; + + for (let i = 0; i < updatesWithChecksum.length; i++) { + const { firmwareTarget: target = 0, data, checksum } = + updatesWithChecksum[i]; + + if (i < skipFinishedFiles) { + // If we are resuming, skip this file since it was already done before + self.driver.controllerLog.logNode( + self.id, + `Skipping already completed firmware update (part ${ + i + 1 + } / ${updatesWithChecksum.length})...`, + ); + sentBytesOfPreviousFiles += data.length; + continue; + } + + self.driver.controllerLog.logNode( + self.id, + `Updating firmware (part ${ + i + 1 + } / ${updatesWithChecksum.length})...`, + ); + + // For determining the initial fragment size, assume the node respects our choice. + // If the node is not secure, these two values are identical anyways. + let fragmentSize = options.nonSecureTransfer + ? fragmentSizeNonSecure + : fragmentSizeSecure; + + // Tell the node to start requesting fragments + const { resume, nonSecureTransfer } = yield* self + .beginFirmwareUpdateInternal( + data, + target, + meta, + fragmentSize, + checksum, + shouldResume, + options.nonSecureTransfer, + ); + + // If the node did not accept non-secure transfer, revisit our choice of fragment size + if (options.nonSecureTransfer && !nonSecureTransfer) { + fragmentSize = fragmentSizeSecure; + } + + // Remember the checksum, so we can resume if necessary + self._previousFirmwareCRC = checksum; + + if (shouldResume) { + self.driver.controllerLog.logNode( + self.id, + `Node ${ + resume ? "accepted" : "did not accept" + } resuming the update...`, + ); + } + if (nonSecureTransfer) { + self.driver.controllerLog.logNode( + self.id, + `Firmware will be transferred without encryption...`, + ); + } + + yield; // Give the task scheduler time to do something else + + // Listen for firmware update fragment requests and handle them + updateResult = yield* self.doFirmwareUpdateInternal( + data, + fragmentSize, + nonSecureTransfer, + abortContext, + (fragment, total) => { + const progress: FirmwareUpdateProgress = { + currentFile: i + 1, + totalFiles: updatesWithChecksum.length, + sentFragments: fragment, + totalFragments: total, + progress: roundTo( + ( + (sentBytesOfPreviousFiles + + Math.min( + fragment * fragmentSize, + data.length, + )) + / totalBytes + ) * 100, + 2, + ), + }; + notifyProgress(progress); + + // When this file is done, add the fragments to the total, so we can compute the total progress correctly + if (fragment === total) { + sentBytesOfPreviousFiles += data.length; + } + }, + ); + + // If we wait, wait a bit longer than the device told us, so it is actually ready to use + conservativeWaitTime = self.driver + .getConservativeWaitTimeAfterFirmwareUpdate( + updateResult.waitTime, + ); + + if (!updateResult.success) { + self.driver.controllerLog.logNode(self.id, { + message: `Firmware update (part ${ + i + 1 + } / ${updatesWithChecksum.length}) failed with status ${ + getEnumMemberName( + FirmwareUpdateStatus, + updateResult.status, + ) + }`, + direction: "inbound", + }); + + const result: FirmwareUpdateResult = { + ...updateResult, + waitTime: undefined, + reInterview: false, + }; + self._emit( + "firmware update finished", + self, + result, + ); + + return result; + } else if (i < updatesWithChecksum.length - 1) { + // Update succeeded, but we're not done yet + + self.driver.controllerLog.logNode(self.id, { + message: `Firmware update (part ${ + i + 1 + } / ${updatesWithChecksum.length}) succeeded with status ${ + getEnumMemberName( + FirmwareUpdateStatus, + updateResult.status, + ) + }`, + direction: "inbound", + }); + + self.driver.controllerLog.logNode( + self.id, + `Continuing with next part in ${conservativeWaitTime} seconds...`, + ); + + // If we've resumed the previous file, there's no need to resume the next one too + shouldResume = false; + + yield () => wait(conservativeWaitTime * 1000, true); + } + } + + // We're done. No need to resume this update + self._previousFirmwareCRC = undefined; + + const result: FirmwareUpdateResult = { + ...updateResult, + waitTime: conservativeWaitTime!, + reInterview: true, + }; + + // After a successful firmware update, we want to interview sleeping nodes immediately, + // so don't send them to sleep when they wake up + keepAwake = true; + + self._emit("firmware update finished", self, result); + + return result; + }, + cleanup() { + self._abortFirmwareUpdate = undefined; + self._firmwareUpdatePrematureRequest = undefined; + + // Make sure that the keepAwake flag gets reset at the end + self.keepAwake = keepAwake; + if (!keepAwake) { + setImmediate(() => { + self.driver.debounceSendNodeToSleep(self); + }); + } + + return Promise.resolve(); + }, + }; + } + + /** Prepares the firmware update of a single target by collecting the necessary information */ + private async prepareFirmwareUpdateInternal( + targets: number[], + abortContext: AbortFirmwareUpdateContext, + ): Promise< + | undefined + | (FirmwareUpdateMetaData & { + fragmentSizeSecure: number; + fragmentSizeNonSecure: number; + }) + > { + const api = this.commandClasses["Firmware Update Meta Data"]; + + // ================================ + // STEP 1: + // Check if this update is possible + const meta = await api.getMetaData(); + if (!meta) { + throw new ZWaveError( + `Failed to start the update: The node did not respond in time!`, + ZWaveErrorCodes.Controller_NodeTimeout, + ); + } + + for (const target of targets) { + if (target === 0) { + if (!meta.firmwareUpgradable) { + throw new ZWaveError( + `Failed to start the update: The Z-Wave chip firmware is not upgradable!`, + ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable, + ); + } + } else { + if (api.version < 3) { + throw new ZWaveError( + `Failed to start the update: The node does not support upgrading a different firmware target than 0!`, + ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound, + ); + } else if ( + meta.additionalFirmwareIDs[target - 1] == undefined + ) { + throw new ZWaveError( + `Failed to start the update: Firmware target #${target} not found on this node!`, + ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound, + ); + } + } + } + + // ================================ + // STEP 2: + // Determine the fragment size + const fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id }); + fcc.toggleEncapsulationFlag( + EncapsulationFlags.Security, + this.driver.isCCSecure(fcc.ccId, this.id), + ); + const maxGrossPayloadSizeSecure = this.driver + .computeNetCCPayloadSize( + fcc, + ); + const maxGrossPayloadSizeNonSecure = this.driver + .computeNetCCPayloadSize(fcc, true); + + const ccVersion = getEffectiveCCVersion(this.driver, fcc); + const maxNetPayloadSizeSecure = maxGrossPayloadSizeSecure + - 2 // report number + - (ccVersion >= 2 ? 2 : 0); // checksum + const maxNetPayloadSizeNonSecure = maxGrossPayloadSizeNonSecure + - 2 // report number + - (ccVersion >= 2 ? 2 : 0); // checksum + + // Use the smallest allowed payload + const fragmentSizeSecure = Math.min( + maxNetPayloadSizeSecure, + meta.maxFragmentSize ?? Number.POSITIVE_INFINITY, + ); + const fragmentSizeNonSecure = Math.min( + maxNetPayloadSizeNonSecure, + meta.maxFragmentSize ?? Number.POSITIVE_INFINITY, + ); + + if (abortContext.abort) { + abortContext.abortPromise.resolve(true); + return; + } else { + return { + ...meta, + fragmentSizeSecure, + fragmentSizeNonSecure, + }; + } + } + + protected async handleUnexpectedFirmwareUpdateGet( + command: FirmwareUpdateMetaDataCCGet, + ): Promise { + // This method will only be called under two circumstances: + // 1. The node is currently busy responding to a firmware update request -> remember the request + if (this.isFirmwareUpdateInProgress()) { + this._firmwareUpdatePrematureRequest = command; + return; + } + + // 2. No firmware update is in progress -> abort + this.driver.controllerLog.logNode(this.id, { + message: + `Received Firmware Update Get, but no firmware update is in progress. Forcing the node to abort...`, + direction: "inbound", + }); + + // Since no update is in progress, we need to determine the fragment size again + const fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id }); + fcc.toggleEncapsulationFlag( + EncapsulationFlags.Security, + !!(command.encapsulationFlags & EncapsulationFlags.Security), + ); + const ccVersion = getEffectiveCCVersion(this.driver, fcc); + const fragmentSize = this.driver.computeNetCCPayloadSize(fcc) + - 2 // report number + - (ccVersion >= 2 ? 2 : 0); // checksum + const fragment = randomBytes(fragmentSize); + try { + await this.sendCorruptedFirmwareUpdateReport( + command.reportNumber, + fragment, + ); + } catch { + // ignore + } + } + + /** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */ + private async *beginFirmwareUpdateInternal( + data: Buffer, + target: number, + meta: FirmwareUpdateMetaData, + fragmentSize: number, + checksum: number, + resume: boolean | undefined, + nonSecureTransfer: boolean | undefined, + ) { + const api = this.commandClasses["Firmware Update Meta Data"]; + + // ================================ + // STEP 3: + // Start the update + this.driver.controllerLog.logNode(this.id, { + message: `Starting firmware update...`, + direction: "outbound", + }); + + // Request the node to start the upgrade + await api.requestUpdate({ + // TODO: Should manufacturer id and firmware id be provided externally? + manufacturerId: meta.manufacturerId, + firmwareId: target == 0 + ? meta.firmwareId + : meta.additionalFirmwareIDs[target - 1], + firmwareTarget: target, + fragmentSize, + checksum, + resume, + nonSecureTransfer, + }); + // Pause the task until the response is received, because that can take + // up to a minute + const result: FirmwareUpdateMetaDataCCRequestReport = yield () => + this.driver + .waitForCommand( + (cc) => + cc instanceof FirmwareUpdateMetaDataCCRequestReport + && cc.nodeId === this.id, + 60000, + ); + + switch (result.status) { + case FirmwareUpdateRequestStatus.Error_AuthenticationExpected: + throw new ZWaveError( + `Failed to start the update: A manual authentication event (e.g. button push) was expected!`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, + ); + case FirmwareUpdateRequestStatus.Error_BatteryLow: + throw new ZWaveError( + `Failed to start the update: The battery level is too low!`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, + ); + case FirmwareUpdateRequestStatus + .Error_FirmwareUpgradeInProgress: + throw new ZWaveError( + `Failed to start the update: A firmware upgrade is already in progress!`, + ZWaveErrorCodes.FirmwareUpdateCC_Busy, + ); + case FirmwareUpdateRequestStatus + .Error_InvalidManufacturerOrFirmwareID: + throw new ZWaveError( + `Failed to start the update: Invalid manufacturer or firmware id!`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, + ); + case FirmwareUpdateRequestStatus.Error_InvalidHardwareVersion: + throw new ZWaveError( + `Failed to start the update: Invalid hardware version!`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, + ); + case FirmwareUpdateRequestStatus.Error_NotUpgradable: + throw new ZWaveError( + `Failed to start the update: Firmware target #${target} is not upgradable!`, + ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable, + ); + case FirmwareUpdateRequestStatus.Error_FragmentSizeTooLarge: + throw new ZWaveError( + `Failed to start the update: The chosen fragment size is too large!`, + ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart, + ); + case FirmwareUpdateRequestStatus.OK: + // All good, we have started! + // Keep the node awake until the update is done. + this.keepAwake = true; + } + + return { + resume: !!result.resume, + nonSecureTransfer: !!result.nonSecureTransfer, + }; + } + + protected async handleFirmwareUpdateMetaDataGet( + command: FirmwareUpdateMetaDataCCMetaDataGet, + ): Promise { + const endpoint = this.getEndpoint(command.endpointIndex) + ?? this; + + // We are being queried, so the device may actually not support the CC, just control it. + // Using the commandClasses property would throw in that case + const api = endpoint + .createAPI(CommandClasses["Firmware Update Meta Data"], false) + .withOptions({ + // Answer with the same encapsulation as asked, but omit + // Supervision as it shouldn't be used for Get-Report flows + encapsulationFlags: command.encapsulationFlags + & ~EncapsulationFlags.Supervision, + }); + + // We do not support the firmware to be upgraded. + await api.reportMetaData({ + manufacturerId: this.driver.options.vendor?.manufacturerId + ?? 0xffff, + firmwareUpgradable: false, + hardwareVersion: this.driver.options.vendor?.hardwareVersion + ?? 0, + }); + } + + private async sendCorruptedFirmwareUpdateReport( + reportNum: number, + fragment: Buffer, + nonSecureTransfer: boolean = false, + ): Promise { + try { + await this.commandClasses["Firmware Update Meta Data"] + .withOptions({ + // Only encapsulate if the transfer is secure + autoEncapsulate: !nonSecureTransfer, + }) + .sendFirmwareFragment(reportNum, true, fragment); + } catch { + // ignore + } + } + + private hasPendingFirmwareUpdateFragment( + fragmentNumber: number, + ): boolean { + // Avoid queuing duplicate fragments + const isCurrentFirmwareFragment = (t: Transaction) => + t.message.getNodeId() === this.id + && isCommandClassContainer(t.message) + && t.message.command instanceof FirmwareUpdateMetaDataCCReport + && t.message.command.reportNumber === fragmentNumber; + + return this.driver.hasPendingTransactions( + isCurrentFirmwareFragment, + ); + } + + private async *doFirmwareUpdateInternal( + data: Buffer, + fragmentSize: number, + nonSecureTransfer: boolean, + abortContext: AbortFirmwareUpdateContext, + onProgress: (fragment: number, total: number) => void, + ): AsyncGenerator< + any, + PartialFirmwareUpdateResult, + any + > { + const numFragments = Math.ceil(data.length / fragmentSize); + + // Make sure we're not responding to an outdated request immediately + this._firmwareUpdatePrematureRequest = undefined; + + // ================================ + // STEP 4: + // Respond to fragment requests from the node + update: while (true) { + yield; // Give the task scheduler time to do something else + + // During ongoing firmware updates, it can happen that the next request is received before the callback for the previous response + // is back. In that case we can immediately handle the premature request. Otherwise wait for the next request. + let fragmentRequest: FirmwareUpdateMetaDataCCGet; + if (this._firmwareUpdatePrematureRequest) { + fragmentRequest = this._firmwareUpdatePrematureRequest; + this._firmwareUpdatePrematureRequest = undefined; + } else { + try { + fragmentRequest = yield () => + this.driver + .waitForCommand( + (cc) => + cc.nodeId === this.id + && cc + instanceof FirmwareUpdateMetaDataCCGet, + // Wait up to 2 minutes for each fragment request. + // Some users try to update devices with unstable connections, where 30s can be too short. + timespan.minutes(2), + ); + } catch { + // In some cases it can happen that the device stops requesting update frames + // We need to timeout the update in this case so it can be restarted + this.driver.controllerLog.logNode(this.id, { + message: `Firmware update timed out`, + direction: "none", + level: "warn", + }); + + return { + success: false, + status: FirmwareUpdateStatus.Error_Timeout, + }; + } + } + + // When a node requests a firmware update fragment, it must be awake + this.markAsAwake(); + + if (fragmentRequest.reportNumber > numFragments) { + this.driver.controllerLog.logNode(this.id, { + message: + `Received Firmware Update Get for an out-of-bounds fragment. Forcing the node to abort...`, + direction: "inbound", + }); + await this.sendCorruptedFirmwareUpdateReport( + fragmentRequest.reportNumber, + randomBytes(fragmentSize), + nonSecureTransfer, + ); + // This will cause the node to abort the process, wait for that + break update; + } + + // Actually send the requested frames + request: for ( + let num = fragmentRequest.reportNumber; + num + < fragmentRequest.reportNumber + + fragmentRequest.numReports; + num++ + ) { + yield; // Give the task scheduler time to do something else + + // Check if the node requested more fragments than are left + if (num > numFragments) { + break; + } + const fragment = data.subarray( + (num - 1) * fragmentSize, + num * fragmentSize, + ); + + if (abortContext.abort) { + await this.sendCorruptedFirmwareUpdateReport( + fragmentRequest.reportNumber, + randomBytes(fragment.length), + nonSecureTransfer, + ); + // This will cause the node to abort the process, wait for that + break update; + } else { + // Avoid queuing duplicate fragments + if (this.hasPendingFirmwareUpdateFragment(num)) { + this.driver.controllerLog.logNode(this.id, { + message: `Firmware fragment ${num} already queued`, + level: "warn", + }); + continue request; + } + + this.driver.controllerLog.logNode(this.id, { + message: + `Sending firmware fragment ${num} / ${numFragments}`, + direction: "outbound", + }); + const isLast = num === numFragments; + + try { + await this + .commandClasses["Firmware Update Meta Data"] + .withOptions({ + // Only encapsulate if the transfer is secure + autoEncapsulate: !nonSecureTransfer, + }) + .sendFirmwareFragment(num, isLast, fragment); + + onProgress(num, numFragments); + + // If that was the last one wait for status report from the node and restart interview + if (isLast) { + abortContext.tooLateToAbort = true; + break update; + } + } catch { + // When transmitting fails, simply stop responding to this request and wait for the node to re-request the fragment + this.driver.controllerLog.logNode(this.id, { + message: + `Failed to send firmware fragment ${num} / ${numFragments}`, + direction: "outbound", + level: "warn", + }); + break request; + } + } + } + } + + yield; // Give the task scheduler time to do something else + + // ================================ + // STEP 5: + // Finalize the update process + + const statusReport: + | FirmwareUpdateMetaDataCCStatusReport + | undefined = yield () => + this.driver + .waitForCommand( + (cc) => + cc.nodeId === this.id + && cc + instanceof FirmwareUpdateMetaDataCCStatusReport, + // Wait up to 5 minutes. It should never take that long, but the specs + // don't say anything specific + 5 * 60000, + ) + .catch(() => undefined); + + if (abortContext.abort) { + abortContext.abortPromise.resolve( + statusReport?.status + === FirmwareUpdateStatus.Error_TransmissionFailed, + ); + } + + if (!statusReport) { + this.driver.controllerLog.logNode( + this.id, + `The node did not acknowledge the completed update`, + "warn", + ); + + return { + success: false, + status: FirmwareUpdateStatus.Error_Timeout, + }; + } + + const { status, waitTime } = statusReport; + + // Actually, OK_WaitingForActivation should never happen since we don't allow + // delayed activation in the RequestGet command + const success = status >= FirmwareUpdateStatus.OK_WaitingForActivation; + + return { + success, + status, + waitTime, + }; + } +} diff --git a/packages/zwave-js/src/lib/node/mixins/README.md b/packages/zwave-js/src/lib/node/mixins/README.md new file mode 100644 index 000000000000..e5519f2b9e61 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/README.md @@ -0,0 +1,3 @@ +This folder contains "mixins" to compose the full functionality of the `ZWaveNode` class. They are not really mixins in the traditional sense, but rather "partial" classes that are extended in a fixed order to compose the full `ZWaveNode` class from manageable chunks. + +Files should be prefixed with numbers to indicate the order in which the inheritance is done. Each file should export a class that extends the previous one. diff --git a/packages/zwave-js/src/lib/node/mixins/index.ts b/packages/zwave-js/src/lib/node/mixins/index.ts new file mode 100644 index 000000000000..0843bf7efb64 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/index.ts @@ -0,0 +1,3 @@ +import { FirmwareUpdateMixin } from "./70_FirmwareUpdate"; + +export abstract class ZWaveNodeMixins extends FirmwareUpdateMixin {} diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/Basic.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/Basic.ts index cccd5e04a575..4a4dc48c3467 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/Basic.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/Basic.ts @@ -15,8 +15,8 @@ const respondToBasicGet: MockNodeBehavior = { return; } - const cc = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: (self.state.get(StateKeys.currentValue) ?? 0) as number, }); diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySensor.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySensor.ts index 93b02bb9d0ed..9fb8c0d4a1b0 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySensor.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySensor.ts @@ -23,8 +23,8 @@ const respondToBinarySensorSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new BinarySensorCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySensorCCSupportedReport({ + nodeId: controller.ownNodeId, supportedSensorTypes: capabilities.supportedSensorTypes, }); return { action: "sendCC", cc }; @@ -56,8 +56,8 @@ const respondToBinarySensorGet: MockNodeBehavior = { if (sensorType != undefined) { const value = capabilities.getValue?.(sensorType) ?? false; - const cc = new BinarySensorCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySensorCCReport({ + nodeId: controller.ownNodeId, type: sensorType, value, }); diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySwitch.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySwitch.ts index 416dc35a2e09..49b649f8f937 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySwitch.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/BinarySwitch.ts @@ -32,8 +32,8 @@ const respondToBinarySwitchGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new BinarySwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: controller.ownNodeId, currentValue: ( self.state.get(StateKeys.currentValue) ?? capabilities.defaultValue diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ColorSwitch.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ColorSwitch.ts index ffcbad8ee159..ec0f63565677 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ColorSwitch.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ColorSwitch.ts @@ -35,8 +35,8 @@ const respondToColorSwitchSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new ColorSwitchCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ColorSwitchCCSupportedReport({ + nodeId: controller.ownNodeId, supportedColorComponents: Object.keys( capabilities.colorComponents, ).map( @@ -74,8 +74,8 @@ const respondToColorSwitchGet: MockNodeBehavior = { }; const component = receivedCC.colorComponent; if (component in capabilities.colorComponents) { - const cc = new ColorSwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ColorSwitchCCReport({ + nodeId: controller.ownNodeId, colorComponent: component, currentValue: (self.state.get(StateKeys.component(component)) diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/Configuration.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/Configuration.ts index 6fa5b3dc00b7..7eb5ce3ede84 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/Configuration.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/Configuration.ts @@ -50,8 +50,8 @@ const respondToConfigurationGet: MockNodeBehavior = { ?? paramInfo.defaultValue ?? 0; - const cc = new ConfigurationCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ConfigurationCCReport({ + nodeId: controller.ownNodeId, parameter, value, valueSize: paramInfo.valueSize, @@ -137,8 +137,8 @@ const respondToConfigurationNameGet: MockNodeBehavior = { // Do nothing if the parameter is not supported if (!paramInfo) return { action: "fail" }; - const cc = new ConfigurationCCNameReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ConfigurationCCNameReport({ + nodeId: controller.ownNodeId, parameter, name: paramInfo.name ?? "", reportsToFollow: 0, @@ -165,8 +165,8 @@ const respondToConfigurationInfoGet: MockNodeBehavior = { // Do nothing if the parameter is not supported if (!paramInfo) return { action: "fail" }; - const cc = new ConfigurationCCInfoReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ConfigurationCCInfoReport({ + nodeId: controller.ownNodeId, parameter, info: paramInfo.info ?? "", reportsToFollow: 0, @@ -197,16 +197,16 @@ const respondToConfigurationPropertiesGet: MockNodeBehavior = { // If the parameter is not supported, respond with the first supported parameter if (!paramInfo) { - cc = new ConfigurationCCPropertiesReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ConfigurationCCPropertiesReport({ + nodeId: controller.ownNodeId, parameter, valueFormat: 0, valueSize: 0, nextParameter: nextParameter?.["#"] ?? 0, }); } else { - cc = new ConfigurationCCPropertiesReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ConfigurationCCPropertiesReport({ + nodeId: controller.ownNodeId, parameter, valueSize: paramInfo.valueSize, valueFormat: paramInfo.format diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/EnergyProduction.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/EnergyProduction.ts index a25b3b9bf62c..fcf27572380c 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/EnergyProduction.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/EnergyProduction.ts @@ -52,8 +52,8 @@ const respondToEnergyProductionGet: MockNodeBehavior = { ) as unknown as keyof typeof capabilities.values ]; - const cc = new EnergyProductionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new EnergyProductionCCReport({ + nodeId: controller.ownNodeId, parameter: receivedCC.parameter, value: result?.value ?? 0, scale: getEnergyProductionScale( diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ManufacturerSpecific.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ManufacturerSpecific.ts index 1ca966debef4..ad537d1ceed9 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ManufacturerSpecific.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ManufacturerSpecific.ts @@ -7,7 +7,7 @@ import { type MockNodeBehavior } from "@zwave-js/testing"; const respondToManufacturerSpecificGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof ManufacturerSpecificCCGet) { - const cc = new ManufacturerSpecificCCReport(self.host, { + const cc = new ManufacturerSpecificCCReport({ nodeId: self.id, manufacturerId: self.capabilities.manufacturerId, productType: self.capabilities.productType, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts index b3bb9097d1f9..81424ce67ff2 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts @@ -29,8 +29,8 @@ const respondToMeterSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new MeterCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MeterCCSupportedReport({ + nodeId: controller.ownNodeId, type: capabilities.meterType, supportedScales: capabilities.supportedScales, supportedRateTypes: capabilities.supportedRateTypes, @@ -68,8 +68,8 @@ const respondToMeterGet: MockNodeBehavior = { } : value; - const cc = new MeterCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MeterCCReport({ + nodeId: controller.ownNodeId, type: capabilities.meterType, scale, rateType, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts index 1017522d3d84..f968c22693aa 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts @@ -44,7 +44,7 @@ const encapsulateMultiChannelCC: MockNodeBehavior = { (multiChannelEncap as MultiChannelCCCommandEncapsulation) .destination as number; - response.cc = new MultiChannelCCCommandEncapsulation(self.host, { + response.cc = new MultiChannelCCCommandEncapsulation({ nodeId: response.cc.nodeId, encapsulated: response.cc, endpoint: source, @@ -59,8 +59,8 @@ const encapsulateMultiChannelCC: MockNodeBehavior = { const respondToMultiChannelCCEndPointGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof MultiChannelCCEndPointGet) { - const cc = new MultiChannelCCEndPointReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultiChannelCCEndPointReport({ + nodeId: controller.ownNodeId, countIsDynamic: false, identicalCapabilities: false, individualCount: self.endpoints.size, @@ -74,8 +74,8 @@ const respondToMultiChannelCCEndPointFind: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof MultiChannelCCEndPointFind) { const request = receivedCC; - const cc = new MultiChannelCCEndPointFindReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultiChannelCCEndPointFindReport({ + nodeId: controller.ownNodeId, genericClass: request.genericClass, specificClass: request.specificClass, foundEndpoints: [...self.endpoints.keys()], @@ -92,8 +92,8 @@ const respondToMultiChannelCCCapabilityGet: MockNodeBehavior = { const endpoint = self.endpoints.get( receivedCC.requestedEndpoint, )!; - const cc = new MultiChannelCCCapabilityReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultiChannelCCCapabilityReport({ + nodeId: controller.ownNodeId, endpointIndex: endpoint.index, genericDeviceClass: endpoint?.capabilities.genericDeviceClass ?? self.capabilities.genericDeviceClass, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSensor.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSensor.ts index 417b16a4dc5f..ab824a4bd05a 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSensor.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSensor.ts @@ -24,8 +24,8 @@ const respondToMultilevelSensorGetSupportedSensor: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new MultilevelSensorCCSupportedSensorReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSensorCCSupportedSensorReport({ + nodeId: controller.ownNodeId, supportedSensorTypes: Object.keys( capabilities.sensors, ).map((t) => parseInt(t)), @@ -48,8 +48,8 @@ const respondToMultilevelSensorGetSupportedScale: MockNodeBehavior = { const sensorType = receivedCC.sensorType; const supportedScales = capabilities.sensors[sensorType]?.supportedScales ?? []; - const cc = new MultilevelSensorCCSupportedScaleReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSensorCCSupportedScaleReport({ + nodeId: controller.ownNodeId, sensorType, supportedScales, }); @@ -79,8 +79,8 @@ const respondToMultilevelSensorGet: MockNodeBehavior = { ?? capabilities.sensors[sensorType].supportedScales[0] ?? 0; const value = capabilities.getValue?.(sensorType, scale) ?? 0; - const cc = new MultilevelSensorCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSensorCCReport({ + nodeId: controller.ownNodeId, type: sensorType, scale, value, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSwitch.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSwitch.ts index 09bbc13d794a..83784ebd9b9a 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSwitch.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSwitch.ts @@ -43,8 +43,8 @@ const respondToMultilevelSwitchGet: MockNodeBehavior = { ?? capabilities.defaultValue ?? UNKNOWN_STATE ) as MaybeUnknown; - const cc = new MultilevelSwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSwitchCCReport({ + nodeId: controller.ownNodeId, currentValue, // We don't support transitioning yet targetValue: currentValue, @@ -73,8 +73,8 @@ const respondToMultilevelSwitchSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new MultilevelSwitchCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSwitchCCSupportedReport({ + nodeId: controller.ownNodeId, switchType: capabilities.primarySwitchType, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/Notification.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/Notification.ts index 6a723b81025a..bed6319a0433 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/Notification.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/Notification.ts @@ -23,8 +23,8 @@ const respondToNotificationSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new NotificationCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new NotificationCCSupportedReport({ + nodeId: controller.ownNodeId, supportsV1Alarm: capabilities.supportsV1Alarm, supportedNotificationTypes: Object.keys( capabilities.notificationTypesAndEvents, @@ -49,8 +49,8 @@ const respondToNotificationEventSupportedGet: MockNodeBehavior = { receivedCC.notificationType in capabilities.notificationTypesAndEvents ) { - const cc = new NotificationCCEventSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new NotificationCCEventSupportedReport({ + nodeId: controller.ownNodeId, notificationType: receivedCC.notificationType, supportedEvents: capabilities.notificationTypesAndEvents[ receivedCC.notificationType diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ScheduleEntryLock.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ScheduleEntryLock.ts index 70aa3181fc58..d539f2a3e2ac 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ScheduleEntryLock.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ScheduleEntryLock.ts @@ -58,8 +58,8 @@ const respondToScheduleEntryLockSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new ScheduleEntryLockCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ScheduleEntryLockCCSupportedReport({ + nodeId: controller.ownNodeId, ...capabilities, }); return { action: "sendCC", cc }; @@ -83,8 +83,8 @@ const respondToScheduleEntryLockTimeOffsetSet: MockNodeBehavior = { const respondToScheduleEntryLockTimeOffsetGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof ScheduleEntryLockCCTimeOffsetGet) { - const cc = new ScheduleEntryLockCCTimeOffsetReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ScheduleEntryLockCCTimeOffsetReport({ + nodeId: controller.ownNodeId, standardOffset: (self.state.get(StateKeys.standardOffset) ?? 0) as number, dstOffset: (self.state.get(StateKeys.dstOffset) ?? 0) as number, @@ -195,8 +195,8 @@ const respondToScheduleEntryLockWeekDayScheduleGet: MockNodeBehavior = { StateKeys.schedule(userId, slotId, kind), ) ?? {}) as AllOrNone; - const cc = new ScheduleEntryLockCCWeekDayScheduleReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ScheduleEntryLockCCWeekDayScheduleReport({ + nodeId: controller.ownNodeId, userId, slotId, ...schedule, @@ -294,8 +294,8 @@ const respondToScheduleEntryLockYearDayScheduleGet: MockNodeBehavior = { StateKeys.schedule(userId, slotId, kind), ) ?? {}) as AllOrNone; - const cc = new ScheduleEntryLockCCYearDayScheduleReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ScheduleEntryLockCCYearDayScheduleReport({ + nodeId: controller.ownNodeId, userId, slotId, ...schedule, @@ -394,15 +394,12 @@ const respondToScheduleEntryLockDailyRepeatingScheduleGet: MockNodeBehavior = { StateKeys.schedule(userId, slotId, kind), ) ?? {}) as AllOrNone; - const cc = new ScheduleEntryLockCCDailyRepeatingScheduleReport( - self.host, - { - nodeId: controller.host.ownNodeId, - userId, - slotId, - ...schedule, - }, - ); + const cc = new ScheduleEntryLockCCDailyRepeatingScheduleReport({ + nodeId: controller.ownNodeId, + userId, + slotId, + ...schedule, + }); return { action: "sendCC", cc }; } }, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/SoundSwitch.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/SoundSwitch.ts index ffca6b9a851e..876e4e708506 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/SoundSwitch.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/SoundSwitch.ts @@ -47,8 +47,8 @@ const respondToSoundSwitchConfigurationGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new SoundSwitchCCConfigurationReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SoundSwitchCCConfigurationReport({ + nodeId: controller.ownNodeId, defaultToneId: (self.state.get(StateKeys.defaultToneId) as number) ?? capabilities.defaultToneId, @@ -87,8 +87,8 @@ const respondToSoundSwitchToneNumberGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new SoundSwitchCCTonesNumberReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SoundSwitchCCTonesNumberReport({ + nodeId: controller.ownNodeId, toneCount: capabilities.tones.length, }); return { action: "sendCC", cc }; @@ -108,8 +108,8 @@ const respondToSoundSwitchToneInfoGet: MockNodeBehavior = { }; const tone = capabilities.tones[receivedCC.toneId - 1]; if (tone) { - const cc = new SoundSwitchCCToneInfoReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SoundSwitchCCToneInfoReport({ + nodeId: controller.ownNodeId, toneId: receivedCC.toneId, ...tone, }); @@ -176,14 +176,11 @@ const respondToSoundSwitchTonePlaySet: MockNodeBehavior = { self.state.delete(StateKeys.state); // Tell the controller that we're done playing - const cc = new SoundSwitchCCTonePlayReport( - self.host, - { - nodeId: controller.host.ownNodeId, - toneId: 0, - volume: 0, - }, - ); + const cc = new SoundSwitchCCTonePlayReport({ + nodeId: controller.ownNodeId, + toneId: 0, + volume: 0, + }); await self.sendToController( createMockZWaveRequestFrame(cc, { ackRequested: false, @@ -207,14 +204,11 @@ const respondToSoundSwitchTonePlayGet: MockNodeBehavior = { StateKeys.state, ) as SoundSwitchState | undefined; - const cc = new SoundSwitchCCTonePlayReport( - self.host, - { - nodeId: controller.host.ownNodeId, - toneId: currentState?.toneId ?? 0, - volume: currentState?.volume ?? 0, - }, - ); + const cc = new SoundSwitchCCTonePlayReport({ + nodeId: controller.ownNodeId, + toneId: currentState?.toneId ?? 0, + volume: currentState?.volume ?? 0, + }); return { action: "sendCC", cc }; } diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatMode.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatMode.ts index 3bca22f5e634..32c9c58e8d89 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatMode.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatMode.ts @@ -45,8 +45,8 @@ const respondToThermostatModeGet: MockNodeBehavior = { ? self.state.get(StateKeys.manufacturerData) : undefined; - const cc = new ThermostatModeCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ThermostatModeCCReport({ + nodeId: controller.ownNodeId, mode, // @ts-expect-error I know... manufacturerData, @@ -67,8 +67,8 @@ const respondToThermostatModeSupportedGet: MockNodeBehavior = { ), }; - const cc = new ThermostatModeCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ThermostatModeCCSupportedReport({ + nodeId: controller.ownNodeId, supportedModes: capabilities.supportedModes, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetback.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetback.ts index 6abb57ea0a88..794a3e1bab1c 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetback.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetback.ts @@ -34,8 +34,8 @@ const respondToThermostatSetbackGet: MockNodeBehavior = { ?? "Unused" ) as SetbackState; - const cc = new ThermostatSetbackCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ThermostatSetbackCCReport({ + nodeId: controller.ownNodeId, setbackType, setbackState, }); diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetpoint.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetpoint.ts index 2209f732c553..dac9ba334d58 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetpoint.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/ThermostatSetpoint.ts @@ -98,15 +98,15 @@ const respondToThermostatSetpointGet: MockNodeBehavior = { let cc: ThermostatSetpointCCReport; if (value !== undefined) { - cc = new ThermostatSetpointCCReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ThermostatSetpointCCReport({ + nodeId: controller.ownNodeId, type: setpointType, value, scale: scale ?? 0, }); } else { - cc = new ThermostatSetpointCCReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ThermostatSetpointCCReport({ + nodeId: controller.ownNodeId, type: ThermostatSetpointType["N/A"], scale: 0, value: 0, @@ -128,8 +128,8 @@ const respondToThermostatSetpointSupportedGet: MockNodeBehavior = { ), }; - const cc = new ThermostatSetpointCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new ThermostatSetpointCCSupportedReport({ + nodeId: controller.ownNodeId, supportedSetpointTypes: Object.keys(capabilities.setpoints).map( (k) => parseInt(k), ), @@ -155,8 +155,8 @@ const respondToThermostatSetpointCapabilitiesGet: MockNodeBehavior = { let cc: ThermostatSetpointCCCapabilitiesReport; if (setpointCaps) { - cc = new ThermostatSetpointCCCapabilitiesReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ThermostatSetpointCCCapabilitiesReport({ + nodeId: controller.ownNodeId, type: setpointType, minValue: setpointCaps.minValue, maxValue: setpointCaps.maxValue, @@ -164,8 +164,8 @@ const respondToThermostatSetpointCapabilitiesGet: MockNodeBehavior = { maxValueScale: setpointCaps.scale === "°C" ? 0 : 1, }); } else { - cc = new ThermostatSetpointCCCapabilitiesReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ThermostatSetpointCCCapabilitiesReport({ + nodeId: controller.ownNodeId, type: ThermostatSetpointType["N/A"], minValue: 0, maxValue: 0, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts index 7661f011c99b..c72a8369a610 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/UserCode.ts @@ -55,8 +55,8 @@ const respondToUsersNumberGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new UserCodeCCUsersNumberReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new UserCodeCCUsersNumberReport({ + nodeId: controller.ownNodeId, supportedUsers: capabilities.numUsers ?? 1, }); return { action: "sendCC", cc }; @@ -77,8 +77,8 @@ const respondToUserGet: MockNodeBehavior = { const userId = receivedCC.userId; let cc: UserCodeCCReport; if (capabilities.numUsers >= userId) { - cc = new UserCodeCCReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new UserCodeCCReport({ + nodeId: controller.ownNodeId, userId, userIdStatus: (self.state.get( StateKeys.userIdStatus(userId), @@ -88,8 +88,8 @@ const respondToUserGet: MockNodeBehavior = { ) as string, }); } else { - cc = new UserCodeCCReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new UserCodeCCReport({ + nodeId: controller.ownNodeId, userId, userIdStatus: UserIDStatus.StatusNotAvailable, }); @@ -137,8 +137,8 @@ const respondToUserCodeCapabilitiesGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new UserCodeCCCapabilitiesReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new UserCodeCCCapabilitiesReport({ + nodeId: controller.ownNodeId, supportsAdminCode: capabilities.supportsAdminCode!, supportsAdminCodeDeactivation: capabilities .supportsAdminCodeDeactivation!, @@ -165,8 +165,8 @@ const respondToUserCodeKeypadModeGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new UserCodeCCKeypadModeReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new UserCodeCCKeypadModeReport({ + nodeId: controller.ownNodeId, keypadMode: (self.state.get(StateKeys.keypadMode) ?? capabilities.supportedKeypadModes?.[0] ?? KeypadMode.Normal) as KeypadMode, @@ -242,8 +242,8 @@ const respondToUserCodeAdminCodeGet: MockNodeBehavior = { adminCode = self.state.get(StateKeys.adminCode) as string; } - const cc = new UserCodeCCAdminCodeReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new UserCodeCCAdminCodeReport({ + nodeId: controller.ownNodeId, adminCode: adminCode ?? "", }); return { action: "sendCC", cc }; @@ -289,8 +289,8 @@ const respondToUserCodeUserCodeChecksumGet: MockNodeBehavior = { const checksum = data.length > 0 ? CRC16_CCITT(data) : 0x0000; - const cc = new UserCodeCCUserCodeChecksumReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new UserCodeCCUserCodeChecksumReport({ + nodeId: controller.ownNodeId, userCodeChecksum: checksum, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/WindowCovering.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/WindowCovering.ts index a72707d27d06..1ac1647d50ff 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/WindowCovering.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/WindowCovering.ts @@ -22,8 +22,8 @@ const respondToWindowCoveringSupportedGet: MockNodeBehavior = { receivedCC.endpointIndex, ), }; - const cc = new WindowCoveringCCSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new WindowCoveringCCSupportedReport({ + nodeId: controller.ownNodeId, supportedParameters: capabilities.supportedParameters, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/node/utils.ts b/packages/zwave-js/src/lib/node/utils.ts index fc542fa2e1b6..65e84820bd7a 100644 --- a/packages/zwave-js/src/lib/node/utils.ts +++ b/packages/zwave-js/src/lib/node/utils.ts @@ -2,10 +2,13 @@ import { CommandClass } from "@zwave-js/cc"; import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; import { CommandClasses, - type IZWaveEndpoint, - type IZWaveNode, + type ControlsCC, + type EndpointId, + type GetEndpoint, type MaybeNotKnown, + type NodeId, type SetValueOptions, + type SupportsCC, type TranslatedValueID, type ValueID, ZWaveError, @@ -14,120 +17,125 @@ import { applicationCCs, getCCName, } from "@zwave-js/core"; -import type { ZWaveApplicationHost } from "@zwave-js/host"; -import { type Task } from "../driver/Task"; +import type { + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, + HostIDs, +} from "@zwave-js/host"; function getValue( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, valueId: ValueID, ): T | undefined { - return applHost.getValueDB(node.id).getValue(valueId); + return ctx.getValueDB(nodeId).getValue(valueId); } function setValue( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, valueId: ValueID, value: unknown, options?: SetValueOptions, ): void { - return applHost.getValueDB(node.id).setValue(valueId, value, options); + return ctx.getValueDB(nodeId).setValue(valueId, value, options); } export function endpointCountIsDynamic( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): MaybeNotKnown { return getValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.endpointCountIsDynamic.id, ); } export function endpointsHaveIdenticalCapabilities( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): MaybeNotKnown { return getValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.endpointsHaveIdenticalCapabilities.id, ); } export function getIndividualEndpointCount( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): MaybeNotKnown { return getValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.individualEndpointCount.id, ); } export function getAggregatedEndpointCount( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): MaybeNotKnown { return getValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.aggregatedEndpointCount.id, ); } export function getEndpointCount( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): number { return ( - (getIndividualEndpointCount(applHost, node) || 0) - + (getAggregatedEndpointCount(applHost, node) || 0) + (getIndividualEndpointCount(ctx, nodeId) || 0) + + (getAggregatedEndpointCount(ctx, nodeId) || 0) ); } export function setIndividualEndpointCount( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, count: number, ): void { setValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.individualEndpointCount.id, count, ); } export function setAggregatedEndpointCount( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, count: number, ): void { setValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.aggregatedEndpointCount.id, count, ); } export function getEndpointIndizes( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): number[] { let ret = getValue( - applHost, - node, + ctx, + nodeId, MultiChannelCCValues.endpointIndizes.id, ); if (!ret) { // Endpoint indizes not stored, assume sequential endpoints ret = []; - for (let i = 1; i <= getEndpointCount(applHost, node); i++) { + for (let i = 1; i <= getEndpointCount(ctx, nodeId); i++) { ret.push(i); } } @@ -135,18 +143,23 @@ export function getEndpointIndizes( } export function setEndpointIndizes( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, indizes: number[], ): void { - setValue(applHost, node, MultiChannelCCValues.endpointIndizes.id, indizes); + setValue( + ctx, + nodeId, + MultiChannelCCValues.endpointIndizes.id, + indizes, + ); } export function isMultiChannelInterviewComplete( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, ): boolean { - return !!getValue(applHost, node, { + return !!getValue(ctx, nodeId, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, property: "interviewComplete", @@ -154,13 +167,13 @@ export function isMultiChannelInterviewComplete( } export function setMultiChannelInterviewComplete( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + nodeId: number, complete: boolean, ): void { setValue( - applHost, - node, + ctx, + nodeId, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, @@ -170,15 +183,15 @@ export function setMultiChannelInterviewComplete( ); } -export function getAllEndpoints( - applHost: ZWaveApplicationHost, - node: IZWaveNode, -): IZWaveEndpoint[] { - const ret: IZWaveEndpoint[] = [node]; +export function getAllEndpoints( + ctx: GetValueDB, + node: T & GetEndpoint, +): T[] { + const ret: T[] = [node]; // Check if the Multi Channel CC interview for this node is completed, // because we don't have all the endpoint information before that - if (isMultiChannelInterviewComplete(applHost, node)) { - for (const i of getEndpointIndizes(applHost, node)) { + if (isMultiChannelInterviewComplete(ctx, node.nodeId)) { + for (const i of getEndpointIndizes(ctx, node.nodeId)) { const endpoint = node.getEndpoint(i); if (endpoint) ret.push(endpoint); } @@ -188,15 +201,15 @@ export function getAllEndpoints( /** Determines whether the root application CC values should be hidden in favor of endpoint values */ export function shouldHideRootApplicationCCValues( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB & GetDeviceConfig, + nodeId: number, ): boolean { // This is not the case when the root values should explicitly be preserved - const compatConfig = applHost.getDeviceConfig?.(node.id)?.compat; + const compatConfig = ctx.getDeviceConfig?.(nodeId)?.compat; if (compatConfig?.preserveRootApplicationCCValueIDs) return false; // This is not the case when there are no endpoints - const endpointIndizes = getEndpointIndizes(applHost, node); + const endpointIndizes = getEndpointIndizes(ctx, nodeId); if (endpointIndizes.length === 0) return false; // This is not the case when only individual endpoints should be preserved in addition to the root @@ -217,8 +230,8 @@ export function shouldHideRootApplicationCCValues( * Enhances a value id so it can be consumed better by applications */ export function translateValueID( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: GetValueDB, + endpoint: EndpointId, valueId: T, ): T & TranslatedValueID { // Try to retrieve the speaking CC name @@ -228,8 +241,7 @@ export function translateValueID( ...valueId, }; const ccInstance = CommandClass.createInstanceUnchecked( - applHost, - node, + endpoint, valueId.commandClass, ); if (!ccInstance) { @@ -245,14 +257,14 @@ export function translateValueID( // Retrieve the speaking property name ret.propertyName = ccInstance.translateProperty( - applHost, + ctx, valueId.property, valueId.propertyKey, ); // Try to retrieve the speaking property key if (valueId.propertyKey != undefined) { const propertyKey = ccInstance.translatePropertyKey( - applHost, + ctx, valueId.property, valueId.propertyKey, ); @@ -297,10 +309,21 @@ export function filterRootApplicationCCValueIDs( /** Returns a list of all value names that are defined on all endpoints of this node */ export function getDefinedValueIDs( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: + & HostIDs + & GetValueDB + & GetDeviceConfig + & GetSupportedCCVersion + & GetNode< + NodeId & GetEndpoint + >, + node: + & NodeId + & SupportsCC + & ControlsCC + & GetEndpoint, ): TranslatedValueID[] { - return getDefinedValueIDsInternal(applHost, node, false); + return getDefinedValueIDsInternal(ctx, node, false); } /** @@ -308,18 +331,34 @@ export function getDefinedValueIDs( * Returns a list of all value names that are defined on all endpoints of this node */ export function getDefinedValueIDsInternal( - applHost: ZWaveApplicationHost, - node: IZWaveNode, + ctx: + & HostIDs + & GetValueDB + & GetDeviceConfig + & GetSupportedCCVersion + & GetNode< + NodeId & GetEndpoint + >, + node: + & NodeId + & SupportsCC + & ControlsCC + & GetEndpoint, includeInternal: boolean = false, ): TranslatedValueID[] { // The controller has no values. Even if some ended up in the cache somehow, do not return any. - if (applHost.isControllerNode(node.id)) return []; + if (node.id === ctx.ownNodeId) return []; let ret: ValueID[] = []; const allowControlled: CommandClasses[] = [ CommandClasses["Scene Activation"], ]; - for (const endpoint of getAllEndpoints(applHost, node)) { + for ( + const endpoint of getAllEndpoints( + ctx, + node, + ) + ) { for (const cc of allCCs) { if ( // Create values only for supported CCs @@ -331,14 +370,13 @@ export function getDefinedValueIDsInternal( || cc === CommandClasses.Basic ) { const ccInstance = CommandClass.createInstanceUnchecked( - applHost, endpoint, cc, ); if (ccInstance) { ret.push( ...ccInstance.getDefinedValueIDs( - applHost, + ctx, includeInternal, ), ); @@ -352,15 +390,10 @@ export function getDefinedValueIDsInternal( // via service discovery mechanisms like mDNS or to users in a GUI. // We do this when there are endpoints that were explicitly preserved - if (shouldHideRootApplicationCCValues(applHost, node)) { + if (shouldHideRootApplicationCCValues(ctx, node.id)) { ret = filterRootApplicationCCValueIDs(ret); } // Translate the remaining value IDs before exposing them to applications - return ret.map((id) => translateValueID(applHost, node, id)); -} - -/** Checks if a task belongs to a route rebuilding process */ -export function isFirmwareUpdateOTATask(t: Task): boolean { - return t.tag?.id === "firmware-update-ota"; + return ret.map((id) => translateValueID(ctx, node, id)); } diff --git a/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts b/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts index fb2517781f87..be1f8c2cf027 100644 --- a/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts @@ -10,12 +10,12 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, gotDeserializationOptions, messageTypes, @@ -50,12 +50,11 @@ export class ApplicationCommandRequest extends Message implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ApplicationCommandRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // first byte is a status flag const status = this.payload[0]; @@ -85,17 +84,20 @@ export class ApplicationCommandRequest extends Message let offset = 1; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += nodeIdBytes; // and a command class const commandLength = this.payload[offset++]; - this.command = CommandClass.from(this.host, { + this.command = CommandClass.from({ data: this.payload.subarray(offset, offset + commandLength), nodeId, origin: options.origin, - frameType: this.frameType, + context: { + sourceNodeId: nodeId, + ...options.ctx, + }, }) as SinglecastCC; } else { // TODO: This logic is unsound @@ -131,7 +133,7 @@ export class ApplicationCommandRequest extends Message return super.getNodeId(); } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const statusByte = (this.frameType === "broadcast" ? ApplicationCommandStatusFlags.TypeBroad : this.frameType === "multicast" @@ -139,10 +141,10 @@ export class ApplicationCommandRequest extends Message : 0) | (this.routedBusy ? ApplicationCommandStatusFlags.RoutedBusy : 0); - const serializedCC = this.command.serialize(); + const serializedCC = this.command.serialize(ctx); const nodeId = encodeNodeID( - this.getNodeId() ?? this.host.ownNodeId, - this.host.nodeIdType, + this.getNodeId() ?? ctx.ownNodeId, + ctx.nodeIdType, ); this.payload = Buffer.concat([ Buffer.from([statusByte]), @@ -151,7 +153,7 @@ export class ApplicationCommandRequest extends Message serializedCC, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts b/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts index c528d7408735..11d61cd26831 100644 --- a/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts @@ -11,13 +11,13 @@ import { parseNodeID, parseNodeUpdatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { type DeserializingMessageConstructor, FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, type SuccessIndicator, @@ -54,8 +54,8 @@ const { @messageTypes(MessageType.Request, FunctionType.ApplicationUpdateRequest) // this is only received, not sent! export class ApplicationUpdateRequest extends Message { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); if (gotDeserializationOptions(options)) { this.updateType = this.payload[0]; @@ -67,7 +67,7 @@ export class ApplicationUpdateRequest extends Message { CommandConstructor && (new.target as any) !== CommandConstructor ) { - return new CommandConstructor(host, options); + return new CommandConstructor(options); } this.payload = this.payload.subarray(1); @@ -78,12 +78,12 @@ export class ApplicationUpdateRequest extends Message { public readonly updateType: ApplicationUpdateTypes; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.updateType]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -97,17 +97,16 @@ export class ApplicationUpdateRequestWithNodeInfo extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ApplicationUpdateRequestWithNodeInfoOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.nodeInformation = parseNodeUpdatePayload( this.payload, - this.host.nodeIdType, + options.ctx.nodeIdType, ); this.nodeId = this.nodeInformation.nodeId; } else { @@ -119,12 +118,12 @@ export class ApplicationUpdateRequestWithNodeInfo public nodeId: number; public nodeInformation: NodeUpdatePayload; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = encodeNodeUpdatePayload( this.nodeInformation, - this.host.nodeIdType, + ctx.nodeIdType, ); - return super.serialize(); + return super.serialize(ctx); } } @@ -153,12 +152,11 @@ export class ApplicationUpdateRequestNodeRemoved extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); - const { nodeId } = parseNodeID(this.payload, host.nodeIdType, 0); + const { nodeId } = parseNodeID(this.payload, options.ctx.nodeIdType, 0); this.nodeId = nodeId; // byte 1/2 is 0, meaning unknown } @@ -170,14 +168,13 @@ class ApplicationUpdateRequestSmartStartHomeIDReceivedBase extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); let offset = 0; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += nodeIdBytes; @@ -245,12 +242,11 @@ export class ApplicationUpdateRequestSUCIdChanged extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); - const { nodeId } = parseNodeID(this.payload, host.nodeIdType, 0); + const { nodeId } = parseNodeID(this.payload, options.ctx.nodeIdType, 0); this.sucNodeID = nodeId; // byte 1/2 is 0, meaning unknown } diff --git a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts b/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts index 859512358209..923f0e17cdbd 100644 --- a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts +++ b/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts @@ -1,20 +1,16 @@ // import "@zwave-js/cc"; -import { createTestingHost } from "@zwave-js/host"; import { Message } from "@zwave-js/serial"; import test from "ava"; test("BridgeApplicationCommandRequest can be parsed without RSSI", async (t) => { - t.timeout(30000); - - const host = createTestingHost(); - // Repro for https://github.com/zwave-js/node-zwave-js/issues/4335 t.notThrows(() => - Message.from(host, { + Message.from({ data: Buffer.from( "011200a80001020a320221340000000000000069", "hex", ), + ctx: {} as any, }) ); }); diff --git a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts b/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts index 98ef572cc3b7..73baeb18babf 100644 --- a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts @@ -13,7 +13,6 @@ import { parseNodeBitMask, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, @@ -33,10 +32,11 @@ export class BridgeApplicationCommandRequest extends Message implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); + this._ownNodeId = options.ctx.ownNodeId; + // if (gotDeserializationOptions(options)) { // first byte is a status flag const status = this.payload[0]; @@ -62,21 +62,24 @@ export class BridgeApplicationCommandRequest extends Message let offset = 1; const { nodeId: destinationNodeId, bytesRead: dstNodeIdBytes } = - parseNodeID(this.payload, host.nodeIdType, offset); + parseNodeID(this.payload, options.ctx.nodeIdType, offset); offset += dstNodeIdBytes; const { nodeId: sourceNodeId, bytesRead: srcNodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += srcNodeIdBytes; // Parse the CC const commandLength = this.payload[offset++]; - this.command = CommandClass.from(this.host, { + this.command = CommandClass.from({ data: this.payload.subarray(offset, offset + commandLength), nodeId: sourceNodeId, origin: options.origin, - frameType: this.frameType, + context: { + sourceNodeId, + ...options.ctx, + }, }) as SinglecastCC; offset += commandLength; @@ -107,6 +110,8 @@ export class BridgeApplicationCommandRequest extends Message public readonly fromForeignHomeId: boolean; public readonly rssi?: RSSI; + private _ownNodeId: number; + // This needs to be writable or unwrapping MultiChannelCCs crashes public command: SinglecastCC; // TODO: why is this a SinglecastCC? @@ -122,7 +127,7 @@ export class BridgeApplicationCommandRequest extends Message if (this.frameType !== "singlecast") { message.type = this.frameType; } - if (this.targetNodeId !== this.host.ownNodeId) { + if (this.targetNodeId !== this._ownNodeId) { if (typeof this.targetNodeId === "number") { message["target node"] = this.targetNodeId; } else if (this.targetNodeId.length === 1) { diff --git a/packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts b/packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts index 3fffcdbd9b5b..10adc96cf2be 100644 --- a/packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts @@ -5,12 +5,12 @@ import { encodeCCList, parseCCList, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, gotDeserializationOptions, messageTypes, @@ -59,10 +59,9 @@ export interface SerialAPIStartedRequestOptions extends MessageBaseOptions { @priority(MessagePriority.Normal) export class SerialAPIStartedRequest extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SerialAPIStartedRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wakeUpReason = this.payload[0]; @@ -108,7 +107,7 @@ export class SerialAPIStartedRequest extends Message { public controlledCCs: CommandClasses[]; public supportsLongRange: boolean = false; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const ccList = encodeCCList(this.supportedCCs, this.controlledCCs); const numCCBytes = ccList.length; @@ -122,7 +121,7 @@ export class SerialAPIStartedRequest extends Message { ccList.copy(this.payload, 6); this.payload[6 + numCCBytes] = this.supportsLongRange ? 0b1 : 0; - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts b/packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts index ee6fd3212a26..e2f58de80106 100644 --- a/packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts @@ -1,5 +1,4 @@ import { type MessageOrCCLogEntry, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, @@ -23,10 +22,9 @@ export class ShutdownRequest extends Message {} @messageTypes(MessageType.Response, FunctionType.Shutdown) export class ShutdownResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts index 3cf9a6370e8a..b3cfe7bcdb9a 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts @@ -1,10 +1,10 @@ import { ControllerCapabilityFlags, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -31,12 +31,11 @@ export interface GetControllerCapabilitiesResponseOptions @messageTypes(MessageType.Response, FunctionType.GetControllerCapabilities) export class GetControllerCapabilitiesResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetControllerCapabilitiesResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const capabilityFlags = this.payload[0]; @@ -76,7 +75,7 @@ export class GetControllerCapabilitiesResponse extends Message { public isStaticUpdateController: boolean; public noNodesIncluded: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([ (this.isSecondary ? ControllerCapabilityFlags.Secondary : 0) | (this.isUsingHomeIdFromOtherNetwork @@ -93,6 +92,6 @@ export class GetControllerCapabilitiesResponse extends Message { ? ControllerCapabilityFlags.NoNodesIncluded : 0), ]); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts index bea2a275ec75..78a34fe8aecf 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts @@ -1,10 +1,10 @@ import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -29,12 +29,11 @@ export interface GetControllerVersionResponseOptions @messageTypes(MessageType.Response, FunctionType.GetControllerVersion) export class GetControllerVersionResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetControllerVersionResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // The payload consists of a zero-terminated string and a uint8 for the controller type @@ -49,12 +48,12 @@ export class GetControllerVersionResponse extends Message { public controllerType: ZWaveLibraryTypes; public libraryVersion: string; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from(`${this.libraryVersion}\0`, "ascii"), Buffer.from([this.controllerType]), ]); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts index dbbe9b26d0c1..e16ed603837c 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts @@ -5,12 +5,12 @@ import { encodeLongRangeNodeBitMask, parseLongRangeNodeBitMask, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -27,12 +27,11 @@ export interface GetLongRangeNodesRequestOptions extends MessageBaseOptions { @priority(MessagePriority.Controller) export class GetLongRangeNodesRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetLongRangeNodesRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.segmentNumber = this.payload[0]; @@ -43,9 +42,9 @@ export class GetLongRangeNodesRequest extends Message { public segmentNumber: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.segmentNumber]); - return super.serialize(); + return super.serialize(ctx); } } @@ -58,12 +57,11 @@ export interface GetLongRangeNodesResponseOptions extends MessageBaseOptions { @messageTypes(MessageType.Response, FunctionType.GetLongRangeNodes) export class GetLongRangeNodesResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetLongRangeNodesResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.moreNodes = this.payload[0] != 0; @@ -95,7 +93,7 @@ export class GetLongRangeNodesResponse extends Message { public segmentNumber: number; public nodeIds: readonly number[]; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe( 3 + NUM_LR_NODEMASK_SEGMENT_BYTES, ); @@ -110,7 +108,7 @@ export class GetLongRangeNodesResponse extends Message { ); nodeBitMask.copy(this.payload, 3); - return super.serialize(); + return super.serialize(ctx); } private listStartNode(): number { diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts index 8bd2d83b8630..dbb5c5a54d3f 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts @@ -1,6 +1,5 @@ import type { ProtocolType } from "@zwave-js/core"; import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, @@ -19,10 +18,9 @@ export class GetProtocolVersionRequest extends Message {} @messageTypes(MessageType.Response, FunctionType.GetProtocolVersion) export class GetProtocolVersionResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.protocolType = this.payload[0]; this.protocolVersion = [ this.payload[1], diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts index dbf295ca63d6..7cfca7b6d858 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts @@ -1,10 +1,10 @@ import { MessagePriority, encodeBitMask, parseBitMask } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -33,12 +33,11 @@ export interface GetSerialApiCapabilitiesResponseOptions @messageTypes(MessageType.Response, FunctionType.GetSerialApiCapabilities) export class GetSerialApiCapabilitiesResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetSerialApiCapabilitiesResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // The first 8 bytes are the api version, manufacturer id, product type and product id @@ -67,7 +66,7 @@ export class GetSerialApiCapabilitiesResponse extends Message { public productId: number; public supportedFunctionTypes: FunctionType[]; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(8 + NUM_FUNCTION_BYTES); const firmwareBytes = this.firmwareVersion @@ -86,6 +85,6 @@ export class GetSerialApiCapabilitiesResponse extends Message { ); functionBitMask.copy(this.payload, 8); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts index c6982f85dedc..aa57539c5d26 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts @@ -12,12 +12,12 @@ import { getChipTypeAndVersion, getZWaveChipType, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -38,12 +38,11 @@ export interface GetSerialApiInitDataResponseOptions @messageTypes(MessageType.Response, FunctionType.GetSerialApiInitData) export class GetSerialApiInitDataResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetSerialApiInitDataResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const apiVersion = this.payload[0]; @@ -117,7 +116,7 @@ export class GetSerialApiInitDataResponse extends Message { public zwaveChipType?: string | UnknownZWaveChipType; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { let chipType: UnknownZWaveChipType | undefined; if (typeof this.zwaveChipType === "string") { chipType = getChipTypeAndVersion(this.zwaveChipType); @@ -152,7 +151,7 @@ export class GetSerialApiInitDataResponse extends Message { this.payload[3 + NUM_NODEMASK_BYTES + 1] = chipType.version; } - return super.serialize(); + return super.serialize(ctx); } // public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts b/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts index 48c74811cb3a..cf7e121a4bc0 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts @@ -1,10 +1,10 @@ import type { MessageOrCCLogEntry } from "@zwave-js/core"; import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageOrigin, MessageType, @@ -17,36 +17,37 @@ import { @messageTypes(MessageType.Request, FunctionType.HardReset) @priority(MessagePriority.Controller) export class HardResetRequestBase extends Message { - public constructor(host: ZWaveHost, options?: MessageOptions) { + public constructor(options?: MessageOptions) { if (gotDeserializationOptions(options)) { if ( options.origin === MessageOrigin.Host && (new.target as any) !== HardResetRequest ) { - return new HardResetRequest(host, options); + return new HardResetRequest(options); } else if ( options.origin !== MessageOrigin.Host && (new.target as any) !== HardResetCallback ) { - return new HardResetCallback(host, options); + return new HardResetCallback(options); } } - super(host, options); + super(options); } } @expectedCallback(FunctionType.HardReset) export class HardResetRequest extends HardResetRequestBase { - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -54,10 +55,9 @@ export class HardResetRequest extends HardResetRequestBase { export class HardResetCallback extends HardResetRequestBase { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; } @@ -65,7 +65,7 @@ export class HardResetCallback extends HardResetRequestBase { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } diff --git a/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts index d8f5b9c1aea1..d77d783a9b92 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts @@ -5,12 +5,12 @@ import { ZWaveErrorCodes, } from "@zwave-js/core"; import { LongRangeChannel } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, type SuccessIndicator, expectedResponse, @@ -29,10 +29,9 @@ export class GetLongRangeChannelRequest extends Message {} @priority(MessagePriority.Controller) export class GetLongRangeChannelResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.channel = this.payload[0]; if (this.payload.length >= 2) { @@ -62,12 +61,11 @@ export interface SetLongRangeChannelRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.SetLongRangeChannel) export class SetLongRangeChannelRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SetLongRangeChannelRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -80,9 +78,9 @@ export class SetLongRangeChannelRequest extends Message { public channel: LongRangeChannel; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.channel]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -100,10 +98,9 @@ export class SetLongRangeChannelResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts b/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts index 71ebbe27a170..a35d3570ab53 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts @@ -1,17 +1,18 @@ -import { createTestingHost } from "@zwave-js/host"; import { Message } from "@zwave-js/serial"; import test from "ava"; import { SerialAPISetup_GetSupportedCommandsResponse } from "./SerialAPISetupMessages"; -const host = createTestingHost(); - test("GetSupportedCommandsResponse with extended bitmask parses correctly (pre-7.19.1 encoding)", (t) => { const data = Buffer.from( "0116010b01fe160103000100000001000000000000000109", "hex", ); - const msg = Message.from(host, { data, sdkVersion: "7.19.0" }); + const msg = Message.from({ + data, + sdkVersion: "7.19.0", + ctx: {} as any, + }); t.true(msg instanceof SerialAPISetup_GetSupportedCommandsResponse); const supported = (msg as SerialAPISetup_GetSupportedCommandsResponse) .supportedCommands; @@ -28,7 +29,11 @@ test("GetSupportedCommandsResponse with extended bitmask parses correctly (post- "hex", ); - const msg = Message.from(host, { data, sdkVersion: "7.19.1" }); + const msg = Message.from({ + data, + sdkVersion: "7.19.1", + ctx: {} as any, + }); t.true(msg instanceof SerialAPISetup_GetSupportedCommandsResponse); const supported = (msg as SerialAPISetup_GetSupportedCommandsResponse) .supportedCommands; diff --git a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts index a783fb392a31..1e048bcc3919 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts @@ -10,9 +10,9 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import type { DeserializingMessageConstructor, + MessageEncodingContext, SuccessIndicator, } from "@zwave-js/serial"; import { @@ -86,8 +86,8 @@ function testResponseForSerialAPISetupRequest( @priority(MessagePriority.Controller) @expectedResponse(testResponseForSerialAPISetupRequest) export class SerialAPISetupRequest extends Message { - public constructor(host: ZWaveHost, options: MessageOptions = {}) { - super(host, options); + public constructor(options: MessageOptions = {}) { + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -100,13 +100,13 @@ export class SerialAPISetupRequest extends Message { public command: SerialAPISetupCommand; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.command]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -126,17 +126,16 @@ export class SerialAPISetupRequest extends Message { @messageTypes(MessageType.Response, FunctionType.SerialAPISetup) export class SerialAPISetupResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.command = this.payload[0]; const CommandConstructor = getSubCommandResponseConstructor( this.command, ); if (CommandConstructor && (new.target as any) !== CommandConstructor) { - return new CommandConstructor(host, options); + return new CommandConstructor(options); } this.payload = this.payload.subarray(1); @@ -163,10 +162,9 @@ export class SerialAPISetup_CommandUnsupportedResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); // The payload contains which command is unsupported this.command = this.payload[0]; } @@ -190,8 +188,8 @@ export class SerialAPISetup_CommandUnsupportedResponse export class SerialAPISetup_GetSupportedCommandsRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetSupportedCommands; } } @@ -201,10 +199,9 @@ export class SerialAPISetup_GetSupportedCommandsResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); if (this.payload.length > 1) { @@ -266,12 +263,11 @@ export class SerialAPISetup_SetTXStatusReportRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetTXStatusReportOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetTxStatusReport; if (gotDeserializationOptions(options)) { @@ -286,10 +282,10 @@ export class SerialAPISetup_SetTXStatusReportRequest public enabled: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.enabled ? 0xff : 0x00]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -307,10 +303,9 @@ export class SerialAPISetup_SetTXStatusReportResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -340,12 +335,11 @@ export interface SerialAPISetup_SetNodeIDTypeOptions @subCommandRequest(SerialAPISetupCommand.SetNodeIDType) export class SerialAPISetup_SetNodeIDTypeRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetNodeIDTypeOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetNodeIDType; if (gotDeserializationOptions(options)) { @@ -360,10 +354,10 @@ export class SerialAPISetup_SetNodeIDTypeRequest extends SerialAPISetupRequest { public nodeIdType: NodeIDType; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.nodeIdType]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -382,10 +376,9 @@ export class SerialAPISetup_SetNodeIDTypeResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -408,8 +401,8 @@ export class SerialAPISetup_SetNodeIDTypeResponse extends SerialAPISetupResponse @subCommandRequest(SerialAPISetupCommand.GetRFRegion) export class SerialAPISetup_GetRFRegionRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetRFRegion; } } @@ -417,10 +410,9 @@ export class SerialAPISetup_GetRFRegionRequest extends SerialAPISetupRequest { @subCommandResponse(SerialAPISetupCommand.GetRFRegion) export class SerialAPISetup_GetRFRegionResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.region = this.payload[0]; } @@ -444,12 +436,11 @@ export interface SerialAPISetup_SetRFRegionOptions extends MessageBaseOptions { @subCommandRequest(SerialAPISetupCommand.SetRFRegion) export class SerialAPISetup_SetRFRegionRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetRFRegionOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetRFRegion; if (gotDeserializationOptions(options)) { @@ -464,9 +455,9 @@ export class SerialAPISetup_SetRFRegionRequest extends SerialAPISetupRequest { public region: RFRegion; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.region]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -483,10 +474,9 @@ export class SerialAPISetup_SetRFRegionResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -509,8 +499,8 @@ export class SerialAPISetup_SetRFRegionResponse extends SerialAPISetupResponse @subCommandRequest(SerialAPISetupCommand.GetPowerlevel) export class SerialAPISetup_GetPowerlevelRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetPowerlevel; } } @@ -520,10 +510,9 @@ export class SerialAPISetup_GetPowerlevelResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); // The values are in 0.1 dBm, signed this.powerlevel = this.payload.readInt8(0) / 10; @@ -560,12 +549,11 @@ export interface SerialAPISetup_SetPowerlevelOptions @subCommandRequest(SerialAPISetupCommand.SetPowerlevel) export class SerialAPISetup_SetPowerlevelRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetPowerlevelOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetPowerlevel; if (gotDeserializationOptions(options)) { @@ -594,13 +582,13 @@ export class SerialAPISetup_SetPowerlevelRequest extends SerialAPISetupRequest { public powerlevel: number; public measured0dBm: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); // The values are in 0.1 dBm this.payload.writeInt8(Math.round(this.powerlevel * 10), 0); this.payload.writeInt8(Math.round(this.measured0dBm * 10), 1); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -621,10 +609,9 @@ export class SerialAPISetup_SetPowerlevelResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -649,8 +636,8 @@ export class SerialAPISetup_SetPowerlevelResponse extends SerialAPISetupResponse export class SerialAPISetup_GetPowerlevel16BitRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetPowerlevel16Bit; } } @@ -660,10 +647,9 @@ export class SerialAPISetup_GetPowerlevel16BitResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 4); // The values are in 0.1 dBm, signed this.powerlevel = this.payload.readInt16BE(0) / 10; @@ -702,12 +688,11 @@ export class SerialAPISetup_SetPowerlevel16BitRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetPowerlevel16BitOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetPowerlevel16Bit; if (gotDeserializationOptions(options)) { @@ -736,13 +721,13 @@ export class SerialAPISetup_SetPowerlevel16BitRequest public powerlevel: number; public measured0dBm: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(4); // The values are in 0.1 dBm this.payload.writeInt16BE(Math.round(this.powerlevel * 10), 0); this.payload.writeInt16BE(Math.round(this.measured0dBm * 10), 2); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -764,10 +749,9 @@ export class SerialAPISetup_SetPowerlevel16BitResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -792,8 +776,8 @@ export class SerialAPISetup_SetPowerlevel16BitResponse export class SerialAPISetup_GetLongRangeMaximumTxPowerRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetLongRangeMaximumTxPower; } } @@ -803,10 +787,9 @@ export class SerialAPISetup_GetLongRangeMaximumTxPowerResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); // The values are in 0.1 dBm, signed this.limit = this.payload.readInt16BE(0) / 10; @@ -840,12 +823,11 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_SetLongRangeMaximumTxPowerOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.SetLongRangeMaximumTxPower; if (gotDeserializationOptions(options)) { @@ -868,12 +850,12 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerRequest /** The maximum LR TX power in dBm */ public limit: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(2); // The values are in 0.1 dBm, signed this.payload.writeInt16BE(Math.round(this.limit * 10), 0); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -894,10 +876,9 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -922,8 +903,8 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerResponse export class SerialAPISetup_GetMaximumPayloadSizeRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetMaximumPayloadSize; } } @@ -933,10 +914,9 @@ export class SerialAPISetup_GetMaximumPayloadSizeResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.maxPayloadSize = this.payload[0]; } @@ -957,8 +937,8 @@ export class SerialAPISetup_GetMaximumPayloadSizeResponse export class SerialAPISetup_GetLongRangeMaximumPayloadSizeRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetLongRangeMaximumPayloadSize; } } @@ -968,10 +948,9 @@ export class SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.maxPayloadSize = this.payload[0]; } @@ -992,8 +971,8 @@ export class SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse export class SerialAPISetup_GetSupportedRegionsRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = SerialAPISetupCommand.GetSupportedRegions; } } @@ -1003,10 +982,9 @@ export class SerialAPISetup_GetSupportedRegionsResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 1); const numRegions = this.payload[0]; @@ -1029,12 +1007,11 @@ export interface SerialAPISetup_GetRegionInfoOptions @subCommandRequest(SerialAPISetupCommand.GetRegionInfo) export class SerialAPISetup_GetRegionInfoRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SerialAPISetup_GetRegionInfoOptions, ) { - super(host, options); + super(options); this.command = SerialAPISetupCommand.GetRegionInfo; if (gotDeserializationOptions(options)) { @@ -1049,9 +1026,9 @@ export class SerialAPISetup_GetRegionInfoRequest extends SerialAPISetupRequest { public region: RFRegion; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.region]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -1071,10 +1048,9 @@ export class SerialAPISetup_GetRegionInfoResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.region = this.payload[0]; this.supportsZWave = !!(this.payload[1] & 0b1); this.supportsLongRange = !!(this.payload[1] & 0b10); diff --git a/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts b/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts index 1168ea195f7c..2fc8f9c42bba 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts @@ -5,11 +5,11 @@ import { encodeCCList, getCCName, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, + type MessageEncodingContext, MessageType, messageTypes, priority, @@ -30,10 +30,9 @@ export interface SetApplicationNodeInformationRequestOptions @priority(MessagePriority.Controller) export class SetApplicationNodeInformationRequest extends Message { public constructor( - host: ZWaveHost, options: SetApplicationNodeInformationRequestOptions, ) { - super(host, options); + super(options); this.isListening = options.isListening; this.genericDeviceClass = options.genericDeviceClass; this.specificDeviceClass = options.specificDeviceClass; @@ -47,7 +46,7 @@ export class SetApplicationNodeInformationRequest extends Message { public supportedCCs: CommandClasses[]; public controlledCCs: CommandClasses[]; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const ccList = encodeCCList(this.supportedCCs, this.controlledCCs); const ccListLength = Math.min(ccList.length, 35); this.payload = Buffer.from([ @@ -58,7 +57,7 @@ export class SetApplicationNodeInformationRequest extends Message { ...ccList.subarray(0, ccListLength), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts b/packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts index a0033d3e0faf..079df6cfc39b 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts @@ -1,10 +1,10 @@ import { MessagePriority, encodeBitMask, parseBitMask } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, gotDeserializationOptions, messageTypes, @@ -24,12 +24,11 @@ const NUM_LONG_RANGE_SHADOW_NODE_IDS = 4; @priority(MessagePriority.Controller) export class SetLongRangeShadowNodeIDsRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | LongRangeShadowNodeIDsRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.shadowNodeIds = parseBitMask( @@ -44,7 +43,7 @@ export class SetLongRangeShadowNodeIDsRequest extends Message { public shadowNodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(1); this.payload = encodeBitMask( this.shadowNodeIds, @@ -54,6 +53,6 @@ export class SetLongRangeShadowNodeIDsRequest extends Message { LONG_RANGE_SHADOW_NODE_IDS_START, ); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts b/packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts index ea89fddfb3ce..75126f6c84f7 100644 --- a/packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts @@ -1,11 +1,11 @@ import type { MessageOrCCLogEntry } from "@zwave-js/core"; import { MessagePriority, encodeNodeID, parseNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -27,14 +27,17 @@ export interface GetControllerIdResponseOptions extends MessageBaseOptions { @messageTypes(MessageType.Response, FunctionType.GetControllerId) export class GetControllerIdResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | GetControllerIdResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // The payload is 4 bytes home id, followed by the controller node id this.homeId = this.payload.readUInt32BE(0); - const { nodeId } = parseNodeID(this.payload, host.nodeIdType, 4); + const { nodeId } = parseNodeID( + this.payload, + options.ctx.nodeIdType, + 4, + ); this.ownNodeId = nodeId; } else { this.homeId = options.homeId; @@ -45,14 +48,14 @@ export class GetControllerIdResponse extends Message { public homeId: number; public ownNodeId: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.ownNodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + const nodeId = encodeNodeID(this.ownNodeId, ctx.nodeIdType); const homeId = Buffer.allocUnsafe(4); homeId.writeUInt32BE(this.homeId, 0); this.payload = Buffer.concat([homeId, nodeId]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts b/packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts index 3a112049ea14..62cc328bc90b 100644 --- a/packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts @@ -5,7 +5,6 @@ import { type RSSI, rssiToString, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, @@ -25,10 +24,9 @@ export class GetBackgroundRSSIRequest extends Message {} @messageTypes(MessageType.Response, FunctionType.GetBackgroundRSSI) export class GetBackgroundRSSIResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.rssiChannel0 = parseRSSI(this.payload, 0); this.rssiChannel1 = parseRSSI(this.payload, 1); this.rssiChannel2 = tryParseRSSI(this.payload, 2); diff --git a/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts b/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts index 12451975aa25..502f3b30a1c1 100644 --- a/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts @@ -4,8 +4,10 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -28,10 +30,9 @@ export interface SetRFReceiveModeRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.SetRFReceiveMode) export class SetRFReceiveModeRequest extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SetRFReceiveModeRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -44,10 +45,10 @@ export class SetRFReceiveModeRequest extends Message { public enabled: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.enabled ? 0x01 : 0x00]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -65,10 +66,9 @@ export class SetRFReceiveModeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts b/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts index a84da19a4f33..97ca50aaae70 100644 --- a/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts @@ -1,10 +1,10 @@ import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, messageTypes, @@ -21,10 +21,9 @@ interface SetSerialApiTimeoutsRequestOptions extends MessageBaseOptions { @priority(MessagePriority.Controller) export class SetSerialApiTimeoutsRequest extends Message { public constructor( - host: ZWaveHost, options: SetSerialApiTimeoutsRequestOptions, ) { - super(host, options); + super(options); this.ackTimeout = options.ackTimeout; this.byteTimeout = options.byteTimeout; } @@ -32,22 +31,21 @@ export class SetSerialApiTimeoutsRequest extends Message { public ackTimeout: number; public byteTimeout: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([ Math.round(this.ackTimeout / 10), Math.round(this.byteTimeout / 10), ]); - return super.serialize(); + return super.serialize(ctx); } } @messageTypes(MessageType.Response, FunctionType.SetSerialApiTimeouts) export class SetSerialApiTimeoutsResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._oldAckTimeout = this.payload[0] * 10; this._oldByteTimeout = this.payload[1] * 10; } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts index 945b465b893a..59609fb46c88 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts @@ -1,16 +1,21 @@ import { type BasicDeviceClass, type CommandClasses, + type ListenBehavior, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, NodeType, Protocols, parseNodeID, parseNodeUpdatePayload, } from "@zwave-js/core"; -import type { ZWaveApplicationHost, ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { GetAllNodes } from "@zwave-js/host"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -68,10 +73,10 @@ interface AddNodeDSKToNetworkRequestOptions extends MessageBaseOptions { } export function computeNeighborDiscoveryTimeout( - host: ZWaveApplicationHost, + host: GetAllNodes, nodeType: NodeType, ): number { - const allNodes = [...host.nodes.values()]; + const allNodes = [...host.getAllNodes()]; const numListeningNodes = allNodes.filter((n) => n.isListening).length; const numFlirsNodes = allNodes.filter((n) => n.isFrequentListening).length; const numNodes = allNodes.length; @@ -89,14 +94,14 @@ export function computeNeighborDiscoveryTimeout( // no expected response, the controller will respond with multiple AddNodeToNetworkRequests @priority(MessagePriority.Controller) export class AddNodeToNetworkRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== AddNodeToNetworkRequestStatusReport ) { - return new AddNodeToNetworkRequestStatusReport(host, options); + return new AddNodeToNetworkRequestStatusReport(options); } - super(host, options); + super(options); } } @@ -130,10 +135,9 @@ function testCallbackForAddNodeRequest( @expectedCallback(testCallbackForAddNodeRequest) export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { public constructor( - host: ZWaveHost, options: AddNodeToNetworkRequestOptions = {}, ) { - super(host, options); + super(options); this.addNodeType = options.addNodeType; this.highPower = !!options.highPower; @@ -147,14 +151,15 @@ export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { /** Whether to include network wide */ public networkWide: boolean = false; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); let data: number = this.addNodeType || AddNodeType.Any; if (this.highPower) data |= AddNodeFlags.HighPower; if (this.networkWide) data |= AddNodeFlags.NetworkWide; this.payload = Buffer.from([data, this.callbackId]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -183,14 +188,14 @@ export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { } export class EnableSmartStartListenRequest extends AddNodeToNetworkRequestBase { - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const control: number = AddNodeType.SmartStartListen | AddNodeFlags.NetworkWide; // The Serial API does not send a callback, so disable waiting for one this.callbackId = 0; this.payload = Buffer.from([control, this.callbackId]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -205,10 +210,9 @@ export class EnableSmartStartListenRequest extends AddNodeToNetworkRequestBase { export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { public constructor( - host: ZWaveHost, options: AddNodeDSKToNetworkRequestOptions, ) { - super(host, options); + super(options); this.nwiHomeId = options.nwiHomeId; this.authHomeId = options.authHomeId; @@ -227,7 +231,8 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { /** Whether to include as long-range or not */ public protocol: Protocols; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); let control: number = AddNodeType.SmartStartDSK; if (this.highPower) control |= AddNodeFlags.HighPower; if (this.networkWide) control |= AddNodeFlags.NetworkWide; @@ -241,7 +246,7 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { this.authHomeId, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -270,10 +275,9 @@ export class AddNodeToNetworkRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.status = this.payload[1]; switch (this.status) { @@ -287,7 +291,7 @@ export class AddNodeToNetworkRequestStatusReport case AddNodeStatus.Done: { const { nodeId } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, 2, ); this.statusContext = { nodeId }; @@ -299,7 +303,7 @@ export class AddNodeToNetworkRequestStatusReport // the payload contains a node information frame this.statusContext = parseNodeUpdatePayload( this.payload.subarray(2), - host.nodeIdType, + options.ctx.nodeIdType, ); break; } @@ -320,7 +324,7 @@ export class AddNodeToNetworkRequestStatusReport ...super.toLogEntry(), message: { status: getEnumMemberName(AddNodeStatus, this.status), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts index de5076baed1c..3ad9ff228202 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts @@ -9,12 +9,12 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, type SuccessIndicator, @@ -29,18 +29,15 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignPriorityReturnRoute) @priority(MessagePriority.Normal) export class AssignPriorityReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== AssignPriorityReturnRouteRequestTransmitReport ) { - return new AssignPriorityReturnRouteRequestTransmitReport( - host, - options, - ); + return new AssignPriorityReturnRouteRequestTransmitReport(options); } - super(host, options); + super(options); } } @@ -59,12 +56,11 @@ export class AssignPriorityReturnRouteRequest extends AssignPriorityReturnRouteRequestBase { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignPriorityReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -99,11 +95,12 @@ export class AssignPriorityReturnRouteRequest public repeaters: number[]; public routeSpeed: ZWaveDataRate; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); const destinationNodeId = encodeNodeID( this.destinationNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); this.payload = Buffer.concat([ nodeId, @@ -118,7 +115,7 @@ export class AssignPriorityReturnRouteRequest ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -134,7 +131,7 @@ export class AssignPriorityReturnRouteRequest ZWaveDataRate, this.routeSpeed, ), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -145,10 +142,9 @@ export class AssignPriorityReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.hasStarted = this.payload[0] !== 0; } @@ -171,10 +167,9 @@ export class AssignPriorityReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; @@ -193,7 +188,7 @@ export class AssignPriorityReturnRouteRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts index 8d3c8a3eb367..4fee6d7b593e 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts @@ -9,12 +9,12 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, type SuccessIndicator, @@ -29,18 +29,17 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignPrioritySUCReturnRoute) @priority(MessagePriority.Normal) export class AssignPrioritySUCReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== AssignPrioritySUCReturnRouteRequestTransmitReport ) { return new AssignPrioritySUCReturnRouteRequestTransmitReport( - host, options, ); } - super(host, options); + super(options); } } @@ -58,12 +57,11 @@ export class AssignPrioritySUCReturnRouteRequest extends AssignPrioritySUCReturnRouteRequestBase { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignPrioritySUCReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -90,8 +88,9 @@ export class AssignPrioritySUCReturnRouteRequest public repeaters: number[]; public routeSpeed: ZWaveDataRate; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); this.payload = Buffer.concat([ nodeId, Buffer.from([ @@ -104,7 +103,7 @@ export class AssignPrioritySUCReturnRouteRequest ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -119,7 +118,7 @@ export class AssignPrioritySUCReturnRouteRequest ZWaveDataRate, this.routeSpeed, ), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -130,10 +129,9 @@ export class AssignPrioritySUCReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.hasStarted = this.payload[0] !== 0; } @@ -156,10 +154,9 @@ export class AssignPrioritySUCReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; @@ -178,7 +175,7 @@ export class AssignPrioritySUCReturnRouteRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts index 7deec2f1ca05..256326a5250c 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts @@ -6,8 +6,11 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; +import type { + INodeQuery, + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -26,14 +29,14 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignReturnRoute) @priority(MessagePriority.Normal) export class AssignReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== AssignReturnRouteRequestTransmitReport ) { - return new AssignReturnRouteRequestTransmitReport(host, options); + return new AssignReturnRouteRequestTransmitReport(options); } - super(host, options); + super(options); } } @@ -48,12 +51,11 @@ export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase implements INodeQuery { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -74,11 +76,12 @@ export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase public nodeId: number; public destinationNodeId: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); const destinationNodeId = encodeNodeID( this.destinationNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); this.payload = Buffer.concat([ @@ -87,7 +90,7 @@ export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase Buffer.from([this.callbackId]), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -96,10 +99,9 @@ export class AssignReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.hasStarted = this.payload[0] !== 0; } @@ -122,10 +124,9 @@ export class AssignReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; @@ -144,7 +145,7 @@ export class AssignReturnRouteRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts index 1eb2f7fc64e5..7b3ceda16802 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts @@ -4,13 +4,13 @@ import { TransmitStatus, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, type INodeQuery, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageOrigin, MessageType, @@ -26,31 +26,29 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.AssignSUCReturnRoute) @priority(MessagePriority.Normal) export class AssignSUCReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if (gotDeserializationOptions(options)) { if ( options.origin === MessageOrigin.Host && (new.target as any) !== AssignSUCReturnRouteRequest ) { - return new AssignSUCReturnRouteRequest(host, options); + return new AssignSUCReturnRouteRequest(options); } else if ( options.origin !== MessageOrigin.Host && (new.target as any) !== AssignSUCReturnRouteRequestTransmitReport ) { - return new AssignSUCReturnRouteRequestTransmitReport( - host, - options, - ); + return new AssignSUCReturnRouteRequestTransmitReport(options); } } - super(host, options); + super(options); } } export interface AssignSUCReturnRouteRequestOptions extends MessageBaseOptions { nodeId: number; + disableCallbackFunctionTypeCheck?: boolean; } function testAssignSUCReturnRouteCallback( @@ -58,13 +56,7 @@ function testAssignSUCReturnRouteCallback( callback: Message, ): boolean { // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute - if ( - callback.host - .getDeviceConfig?.(callback.host.ownNodeId) - ?.compat - ?.disableCallbackFunctionTypeCheck - ?.includes(FunctionType.AssignSUCReturnRoute) - ) { + if (sent.disableCallbackFunctionTypeCheck) { return true; } return callback.functionType === FunctionType.AssignSUCReturnRoute; @@ -76,27 +68,30 @@ export class AssignSUCReturnRouteRequest extends AssignSUCReturnRouteRequestBase implements INodeQuery { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignSUCReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.nodeId = this.payload[0]; this.callbackId = this.payload[1]; } else { this.nodeId = options.nodeId; + this.disableCallbackFunctionTypeCheck = + options.disableCallbackFunctionTypeCheck; } } public nodeId: number; + public readonly disableCallbackFunctionTypeCheck?: boolean; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } } @@ -109,12 +104,11 @@ export class AssignSUCReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignSUCReturnRouteResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wasExecuted = this.payload[0] !== 0; } else { @@ -128,9 +122,9 @@ export class AssignSUCReturnRouteResponse extends Message public wasExecuted: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -153,12 +147,11 @@ export class AssignSUCReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | AssignSUCReturnRouteRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -178,16 +171,17 @@ export class AssignSUCReturnRouteRequestTransmitReport public transmitStatus: TransmitStatus; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId, this.transmitStatus]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts index 55d3e1dcd114..0dc86ed5a5dd 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts @@ -6,8 +6,11 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; +import type { + INodeQuery, + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -26,14 +29,14 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.DeleteReturnRoute) @priority(MessagePriority.Normal) export class DeleteReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== DeleteReturnRouteRequestTransmitReport ) { - return new DeleteReturnRouteRequestTransmitReport(host, options); + return new DeleteReturnRouteRequestTransmitReport(options); } - super(host, options); + super(options); } } @@ -47,12 +50,11 @@ export class DeleteReturnRouteRequest extends DeleteReturnRouteRequestBase implements INodeQuery { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | DeleteReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -65,11 +67,12 @@ export class DeleteReturnRouteRequest extends DeleteReturnRouteRequestBase public nodeId: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } } @@ -78,10 +81,9 @@ export class DeleteReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.hasStarted = this.payload[0] !== 0; } @@ -104,10 +106,9 @@ export class DeleteReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; @@ -126,7 +127,7 @@ export class DeleteReturnRouteRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts index 40960535f621..10ed44691215 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts @@ -4,8 +4,11 @@ import { TransmitStatus, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; +import type { + INodeQuery, + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -25,31 +28,29 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.DeleteSUCReturnRoute) @priority(MessagePriority.Normal) export class DeleteSUCReturnRouteRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if (gotDeserializationOptions(options)) { if ( options.origin === MessageOrigin.Host && (new.target as any) !== DeleteSUCReturnRouteRequest ) { - return new DeleteSUCReturnRouteRequest(host, options); + return new DeleteSUCReturnRouteRequest(options); } else if ( options.origin !== MessageOrigin.Host && (new.target as any) !== DeleteSUCReturnRouteRequestTransmitReport ) { - return new DeleteSUCReturnRouteRequestTransmitReport( - host, - options, - ); + return new DeleteSUCReturnRouteRequestTransmitReport(options); } } - super(host, options); + super(options); } } export interface DeleteSUCReturnRouteRequestOptions extends MessageBaseOptions { nodeId: number; + disableCallbackFunctionTypeCheck?: boolean; } function testDeleteSUCReturnRouteCallback( @@ -57,13 +58,7 @@ function testDeleteSUCReturnRouteCallback( callback: Message, ): boolean { // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute - if ( - callback.host - .getDeviceConfig?.(callback.host.ownNodeId) - ?.compat - ?.disableCallbackFunctionTypeCheck - ?.includes(FunctionType.DeleteSUCReturnRoute) - ) { + if (sent.disableCallbackFunctionTypeCheck) { return true; } return callback.functionType === FunctionType.DeleteSUCReturnRoute; @@ -75,27 +70,30 @@ export class DeleteSUCReturnRouteRequest extends DeleteSUCReturnRouteRequestBase implements INodeQuery { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | DeleteSUCReturnRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.nodeId = this.payload[0]; this.callbackId = this.payload[1]; } else { this.nodeId = options.nodeId; + this.disableCallbackFunctionTypeCheck = + options.disableCallbackFunctionTypeCheck; } } public nodeId: number; + public readonly disableCallbackFunctionTypeCheck?: boolean; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } } @@ -108,12 +106,11 @@ export class DeleteSUCReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | DeleteSUCReturnRouteResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wasExecuted = this.payload[0] !== 0; } else { @@ -127,9 +124,9 @@ export class DeleteSUCReturnRouteResponse extends Message public readonly wasExecuted: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -152,12 +149,11 @@ export class DeleteSUCReturnRouteRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | DeleteSUCReturnRouteRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -177,16 +173,17 @@ export class DeleteSUCReturnRouteRequestTransmitReport public readonly transmitStatus: TransmitStatus; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId, this.transmitStatus]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts index a13ae9071fef..fccd3b49b5ad 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts @@ -12,12 +12,12 @@ import { parseNodeID, parseNodeProtocolInfo, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -35,15 +35,14 @@ interface GetNodeProtocolInfoRequestOptions extends MessageBaseOptions { @priority(MessagePriority.Controller) export class GetNodeProtocolInfoRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetNodeProtocolInfoRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.requestedNodeId = - parseNodeID(this.payload, this.host.nodeIdType, 0).nodeId; + parseNodeID(this.payload, options.ctx.nodeIdType, 0).nodeId; } else { this.requestedNodeId = options.requestedNodeId; } @@ -53,9 +52,9 @@ export class GetNodeProtocolInfoRequest extends Message { // but this is a message to the controller public requestedNodeId: number; - public serialize(): Buffer { - this.payload = encodeNodeID(this.requestedNodeId, this.host.nodeIdType); - return super.serialize(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = encodeNodeID(this.requestedNodeId, ctx.nodeIdType); + return super.serialize(ctx); } } @@ -66,12 +65,11 @@ interface GetNodeProtocolInfoResponseOptions @messageTypes(MessageType.Response, FunctionType.GetNodeProtocolInfo) export class GetNodeProtocolInfoResponse extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | GetNodeProtocolInfoResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { // The context should contain the node ID the protocol info was requested for. @@ -143,7 +141,7 @@ export class GetNodeProtocolInfoResponse extends Message { public genericDeviceClass: number; public specificDeviceClass: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const protocolInfo = encodeNodeProtocolInfo({ isListening: this.isListening, isFrequentListening: this.isFrequentListening, @@ -165,6 +163,6 @@ export class GetNodeProtocolInfoResponse extends Message { ]), ]); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts index ab2705c82e39..4558515637cb 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts @@ -10,12 +10,12 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -33,10 +33,9 @@ export interface GetPriorityRouteRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.GetPriorityRoute) export class GetPriorityRouteRequest extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | GetPriorityRouteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -49,13 +48,13 @@ export class GetPriorityRouteRequest extends Message { public destinationNodeId: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = encodeNodeID( this.destinationNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -71,14 +70,13 @@ export class GetPriorityRouteRequest extends Message { @messageTypes(MessageType.Response, FunctionType.GetPriorityRoute) export class GetPriorityRouteResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); let offset = 0; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += nodeIdBytes; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts index 26431a51ee79..8da062d7f431 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts @@ -5,12 +5,12 @@ import { encodeNodeID, parseNodeBitMask, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, messageTypes, @@ -27,8 +27,8 @@ interface GetRoutingInfoRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.GetRoutingInfo) @priority(MessagePriority.Controller) export class GetRoutingInfoRequest extends Message { - public constructor(host: ZWaveHost, options: GetRoutingInfoRequestOptions) { - super(host, options); + public constructor(options: GetRoutingInfoRequestOptions) { + super(options); this.sourceNodeId = options.nodeId; this.removeNonRepeaters = !!options.removeNonRepeaters; this.removeBadLinks = !!options.removeBadLinks; @@ -38,8 +38,8 @@ export class GetRoutingInfoRequest extends Message { public removeNonRepeaters: boolean; public removeBadLinks: boolean; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.sourceNodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + const nodeId = encodeNodeID(this.sourceNodeId, ctx.nodeIdType); const optionsByte = (this.removeBadLinks ? 0b1000_0000 : 0) | (this.removeNonRepeaters ? 0b0100_0000 : 0); this.payload = Buffer.concat([ @@ -49,7 +49,7 @@ export class GetRoutingInfoRequest extends Message { 0, // callbackId - this must be 0 as per the docs ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -66,10 +66,9 @@ export class GetRoutingInfoRequest extends Message { @messageTypes(MessageType.Response, FunctionType.GetRoutingInfo) export class GetRoutingInfoResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); if (this.payload.length === NUM_NODEMASK_BYTES) { // the payload contains a bit mask of all neighbor nodes diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts index 31402458a911..351262ea3f73 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts @@ -1,10 +1,10 @@ import { MessagePriority, encodeNodeID, parseNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -24,15 +24,14 @@ export interface GetSUCNodeIdResponseOptions extends MessageBaseOptions { @messageTypes(MessageType.Response, FunctionType.GetSUCNodeId) export class GetSUCNodeIdResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | GetSUCNodeIdResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.sucNodeId = parseNodeID( this.payload, - this.host.nodeIdType, + options.ctx.nodeIdType, 0, ).nodeId; } else { @@ -43,8 +42,8 @@ export class GetSUCNodeIdResponse extends Message { /** The node id of the SUC or 0 if none is present */ public sucNodeId: number; - public serialize(): Buffer { - this.payload = encodeNodeID(this.sucNodeId, this.host.nodeIdType); - return super.serialize(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = encodeNodeID(this.sucNodeId, ctx.nodeIdType); + return super.serialize(ctx); } } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts index 6960e2db01da..ab7f7dc71c53 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts @@ -1,10 +1,10 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, messageTypes, @@ -20,27 +20,26 @@ export interface IsFailedNodeRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.IsFailedNode) @priority(MessagePriority.Controller) export class IsFailedNodeRequest extends Message { - public constructor(host: ZWaveHost, options: IsFailedNodeRequestOptions) { - super(host, options); + public constructor(options: IsFailedNodeRequestOptions) { + super(options); this.failedNodeId = options.failedNodeId; } // This must not be called nodeId or rejectAllTransactions may reject the request public failedNodeId: number; - public serialize(): Buffer { - this.payload = encodeNodeID(this.failedNodeId, this.host.nodeIdType); - return super.serialize(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = encodeNodeID(this.failedNodeId, ctx.nodeIdType); + return super.serialize(ctx); } } @messageTypes(MessageType.Response, FunctionType.IsFailedNode) export class IsFailedNodeResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.result = !!this.payload[0]; } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts index 8b8e96e39d82..172a1abaab10 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts @@ -1,6 +1,8 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -44,14 +46,14 @@ export enum RemoveFailedNodeStatus { @messageTypes(MessageType.Request, FunctionType.RemoveFailedNode) @priority(MessagePriority.Controller) export class RemoveFailedNodeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== RemoveFailedNodeRequestStatusReport ) { - return new RemoveFailedNodeRequestStatusReport(host, options); + return new RemoveFailedNodeRequestStatusReport(options); } - super(host, options); + super(options); } } @@ -64,10 +66,9 @@ interface RemoveFailedNodeRequestOptions extends MessageBaseOptions { @expectedCallback(FunctionType.RemoveFailedNode) export class RemoveFailedNodeRequest extends RemoveFailedNodeRequestBase { public constructor( - host: ZWaveHost, options: RemoveFailedNodeRequestOptions, ) { - super(host, options); + super(options); this.failedNodeId = options.failedNodeId; } @@ -75,10 +76,11 @@ export class RemoveFailedNodeRequest extends RemoveFailedNodeRequestBase { /** The node that should be removed */ public failedNodeId: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.failedNodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.failedNodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } } @@ -87,10 +89,9 @@ export class RemoveFailedNodeRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this._removeStatus = this.payload[1]; @@ -111,10 +112,9 @@ export class RemoveFailedNodeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._removeStatus = this.payload[0]; } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts index d5b06589f785..cf62859b1074 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts @@ -3,8 +3,10 @@ import { MessagePriority, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -51,14 +53,14 @@ interface RemoveNodeFromNetworkRequestOptions extends MessageBaseOptions { // no expected response, the controller will respond with multiple RemoveNodeFromNetworkRequests @priority(MessagePriority.Controller) export class RemoveNodeFromNetworkRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== RemoveNodeFromNetworkRequestStatusReport ) { - return new RemoveNodeFromNetworkRequestStatusReport(host, options); + return new RemoveNodeFromNetworkRequestStatusReport(options); } - super(host, options); + super(options); } } @@ -94,10 +96,9 @@ export class RemoveNodeFromNetworkRequest extends RemoveNodeFromNetworkRequestBase { public constructor( - host: ZWaveHost, options: RemoveNodeFromNetworkRequestOptions = {}, ) { - super(host, options); + super(options); this.removeNodeType = options.removeNodeType; this.highPower = !!options.highPower; @@ -111,14 +112,15 @@ export class RemoveNodeFromNetworkRequest /** Whether to exclude network wide */ public networkWide: boolean = false; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); let data: number = this.removeNodeType || RemoveNodeType.Any; if (this.highPower) data |= RemoveNodeFlags.HighPower; if (this.networkWide) data |= RemoveNodeFlags.NetworkWide; this.payload = Buffer.from([data, this.callbackId]); - return super.serialize(); + return super.serialize(ctx); } } @@ -127,10 +129,9 @@ export class RemoveNodeFromNetworkRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.status = this.payload[1]; switch (this.status) { @@ -150,7 +151,7 @@ export class RemoveNodeFromNetworkRequestStatusReport // the payload contains the node ID const { nodeId } = parseNodeID( this.payload.subarray(2), - this.host.nodeIdType, + options.ctx.nodeIdType, ); this.statusContext = { nodeId }; break; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts index 1e3f979b02f8..2514eaafa4f6 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts @@ -1,6 +1,8 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -44,14 +46,14 @@ export enum ReplaceFailedNodeStatus { @messageTypes(MessageType.Request, FunctionType.ReplaceFailedNode) @priority(MessagePriority.Controller) export class ReplaceFailedNodeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== ReplaceFailedNodeRequestStatusReport ) { - return new ReplaceFailedNodeRequestStatusReport(host, options); + return new ReplaceFailedNodeRequestStatusReport(options); } - super(host, options); + super(options); } } @@ -63,10 +65,9 @@ interface ReplaceFailedNodeRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ReplaceFailedNode) export class ReplaceFailedNodeRequest extends ReplaceFailedNodeRequestBase { public constructor( - host: ZWaveHost, options: ReplaceFailedNodeRequestOptions, ) { - super(host, options); + super(options); this.failedNodeId = options.failedNodeId; } @@ -74,10 +75,11 @@ export class ReplaceFailedNodeRequest extends ReplaceFailedNodeRequestBase { /** The node that should be removed */ public failedNodeId: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.failedNodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.failedNodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } } @@ -86,10 +88,9 @@ export class ReplaceFailedNodeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._replaceStatus = this.payload[0]; } @@ -108,10 +109,9 @@ export class ReplaceFailedNodeRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this._replaceStatus = this.payload[1]; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts index 088ad6e69aab..5a058a6619fe 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts @@ -4,13 +4,13 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, type INodeQuery, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, type SuccessIndicator, expectedCallback, @@ -33,10 +33,9 @@ export class RequestNodeInfoResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | RequestNodeInfoResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wasSent = this.payload[0] !== 0; } else { @@ -50,9 +49,9 @@ export class RequestNodeInfoResponse extends Message return this.wasSent; } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.wasSent ? 0x01 : 0]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -84,14 +83,13 @@ function testCallbackForRequestNodeInfoRequest( @priority(MessagePriority.NodeQuery) export class RequestNodeInfoRequest extends Message implements INodeQuery { public constructor( - host: ZWaveHost, options: RequestNodeInfoRequestOptions | MessageDeserializationOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.nodeId = parseNodeID( this.payload, - this.host.nodeIdType, + options.ctx.nodeIdType, 0, ).nodeId; } else { @@ -106,9 +104,9 @@ export class RequestNodeInfoRequest extends Message implements INodeQuery { return false; } - public serialize(): Buffer { - this.payload = encodeNodeID(this.nodeId, this.host.nodeIdType); - return super.serialize(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = encodeNodeID(this.nodeId, ctx.nodeIdType); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts index 8008444c9e1a..93f95cdcb764 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts @@ -1,7 +1,10 @@ import type { MessageOrCCLogEntry } from "@zwave-js/core"; import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { MultiStageCallback, SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MultiStageCallback, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -33,14 +36,14 @@ export interface RequestNodeNeighborUpdateRequestOptions @messageTypes(MessageType.Request, FunctionType.RequestNodeNeighborUpdate) @priority(MessagePriority.Controller) export class RequestNodeNeighborUpdateRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== RequestNodeNeighborUpdateReport ) { - return new RequestNodeNeighborUpdateReport(host, options); + return new RequestNodeNeighborUpdateReport(options); } - super(host, options); + super(options); } } @@ -49,10 +52,9 @@ export class RequestNodeNeighborUpdateRequest extends RequestNodeNeighborUpdateRequestBase { public constructor( - host: ZWaveHost, options: RequestNodeNeighborUpdateRequestOptions, ) { - super(host, options); + super(options); this.nodeId = options.nodeId; this.discoveryTimeout = options.discoveryTimeout; } @@ -60,10 +62,11 @@ export class RequestNodeNeighborUpdateRequest public nodeId: number; public discoveryTimeout: number; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - return super.serialize(); + return super.serialize(ctx); } public getCallbackTimeout(): number | undefined { @@ -73,7 +76,9 @@ export class RequestNodeNeighborUpdateRequest public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), - message: { "callback id": this.callbackId }, + message: { + "callback id": this.callbackId ?? "(not set)", + }, }; } } @@ -83,10 +88,9 @@ export class RequestNodeNeighborUpdateReport implements SuccessIndicator, MultiStageCallback { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this._updateStatus = this.payload[1]; @@ -109,7 +113,7 @@ export class RequestNodeNeighborUpdateReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "update status": getEnumMemberName( NodeNeighborUpdateStatus, this._updateStatus, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts index 4d38e0d3b6cd..849c484748cc 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts @@ -5,12 +5,12 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, type SuccessIndicator, @@ -47,14 +47,14 @@ export enum LearnModeStatus { @messageTypes(MessageType.Request, FunctionType.SetLearnMode) @priority(MessagePriority.Controller) export class SetLearnModeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== SetLearnModeCallback ) { - return new SetLearnModeCallback(host, options); + return new SetLearnModeCallback(options); } - super(host, options); + super(options); } } @@ -66,10 +66,9 @@ export interface SetLearnModeRequestOptions extends MessageBaseOptions { // The callback may come much (30+ seconds), so we wait for it outside of the queue export class SetLearnModeRequest extends SetLearnModeRequestBase { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SetLearnModeRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -82,20 +81,21 @@ export class SetLearnModeRequest extends SetLearnModeRequestBase { public intent: LearnModeIntent; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([ this.intent, this.callbackId, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", intent: getEnumMemberName(LearnModeIntent, this.intent), }, }; @@ -105,10 +105,9 @@ export class SetLearnModeRequest extends SetLearnModeRequestBase { @messageTypes(MessageType.Response, FunctionType.SetLearnMode) export class SetLearnModeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } @@ -130,10 +129,9 @@ export class SetLearnModeCallback extends SetLearnModeRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.status = this.payload[1]; @@ -154,7 +152,7 @@ export class SetLearnModeCallback extends SetLearnModeRequestBase public toLogEntry(): MessageOrCCLogEntry { const message: MessageRecord = { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", status: getEnumMemberName(LearnModeStatus, this.status), }; if ( diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts index 2af0f8077e9e..555a632091bd 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts @@ -10,12 +10,12 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, type SuccessIndicator, expectedResponse, @@ -39,12 +39,11 @@ export type SetPriorityRouteRequestOptions = @expectedResponse(FunctionType.SetPriorityRoute) export class SetPriorityRouteRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | (MessageBaseOptions & SetPriorityRouteRequestOptions), ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -79,10 +78,10 @@ export class SetPriorityRouteRequest extends Message { public repeaters: number[] | undefined; public routeSpeed: ZWaveDataRate | undefined; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { const nodeId = encodeNodeID( this.destinationNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); if (this.repeaters == undefined || this.routeSpeed == undefined) { // Remove the priority route @@ -101,7 +100,7 @@ export class SetPriorityRouteRequest extends Message { ]); } - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -132,14 +131,13 @@ export class SetPriorityRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); // Byte(s) 0/1 are the node ID - this is missing from the Host API specs const { /* nodeId, */ bytesRead } = parseNodeID( this.payload, - this.host.nodeIdType, + options.ctx.nodeIdType, 0, ); diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts index 4b07cbd34061..e4bb2c02473d 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts @@ -6,8 +6,10 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -28,7 +30,8 @@ export enum SetSUCNodeIdStatus { } export interface SetSUCNodeIdRequestOptions extends MessageBaseOptions { - sucNodeId?: number; + ownNodeId: number; + sucNodeId: number; enableSUC: boolean; enableSIS: boolean; transmitOptions?: TransmitOptions; @@ -37,14 +40,14 @@ export interface SetSUCNodeIdRequestOptions extends MessageBaseOptions { @messageTypes(MessageType.Request, FunctionType.SetSUCNodeId) @priority(MessagePriority.Controller) export class SetSUCNodeIdRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== SetSUCNodeIdRequestStatusReport ) { - return new SetSUCNodeIdRequestStatusReport(host, options); + return new SetSUCNodeIdRequestStatusReport(options); } - super(host, options); + super(options); } } @@ -52,21 +55,21 @@ export class SetSUCNodeIdRequestBase extends Message { @expectedCallback(FunctionType.SetSUCNodeId) export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SetSUCNodeIdRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, ZWaveErrorCodes.Deserialization_NotImplemented, ); } else { - this.sucNodeId = options.sucNodeId ?? host.ownNodeId; + this.sucNodeId = options.sucNodeId; this.enableSUC = options.enableSUC; this.enableSIS = options.enableSIS; this.transmitOptions = options.transmitOptions ?? TransmitOptions.DEFAULT; + this._ownNodeId = options.ownNodeId; } } @@ -75,8 +78,11 @@ export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { public enableSIS: boolean; public transmitOptions: TransmitOptions; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.sucNodeId, this.host.nodeIdType); + private _ownNodeId: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.sucNodeId, ctx.nodeIdType); this.payload = Buffer.concat([ nodeId, Buffer.from([ @@ -87,11 +93,11 @@ export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { ]), ]); - return super.serialize(); + return super.serialize(ctx); } public expectsCallback(): boolean { - if (this.sucNodeId === this.host.ownNodeId) return false; + if (this.sucNodeId === this._ownNodeId) return false; return super.expectsCallback(); } } @@ -99,10 +105,9 @@ export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { @messageTypes(MessageType.Response, FunctionType.SetSUCNodeId) export class SetSUCNodeIdResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._wasExecuted = this.payload[0] !== 0; } @@ -127,10 +132,9 @@ export class SetSUCNodeIdRequestStatusReport extends SetSUCNodeIdRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this._status = this.payload[1]; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts index f9b43b6e82f6..750daeb548e9 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts @@ -4,12 +4,12 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -28,12 +28,11 @@ export interface ExtNVMReadLongBufferRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ExtNVMReadLongBuffer) export class ExtNVMReadLongBufferRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtNVMReadLongBufferRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -61,11 +60,11 @@ export class ExtNVMReadLongBufferRequest extends Message { public offset: number; public length: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(5); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.length, 3); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -82,10 +81,9 @@ export class ExtNVMReadLongBufferRequest extends Message { @messageTypes(MessageType.Response, FunctionType.ExtNVMReadLongBuffer) export class ExtNVMReadLongBufferResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.buffer = this.payload; } diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts index 245a6b0b0782..30ed8ca21abd 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts @@ -4,12 +4,12 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -27,12 +27,11 @@ export interface ExtNVMReadLongByteRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ExtNVMReadLongByte) export class ExtNVMReadLongByteRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtNVMReadLongByteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -51,10 +50,10 @@ export class ExtNVMReadLongByteRequest extends Message { public offset: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(3); this.payload.writeUIntBE(this.offset, 0, 3); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -68,10 +67,9 @@ export class ExtNVMReadLongByteRequest extends Message { @messageTypes(MessageType.Response, FunctionType.ExtNVMReadLongByte) export class ExtNVMReadLongByteResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.byte = this.payload[0]; } diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts index ba4f9e47433b..8b27db4fe920 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts @@ -4,12 +4,12 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -30,12 +30,11 @@ export interface ExtNVMWriteLongBufferRequestOptions @expectedResponse(FunctionType.ExtNVMWriteLongBuffer) export class ExtNVMWriteLongBufferRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtNVMWriteLongBufferRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -62,12 +61,12 @@ export class ExtNVMWriteLongBufferRequest extends Message { public offset: number; public buffer: Buffer; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(5 + this.buffer.length); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.buffer.length, 3); this.buffer.copy(this.payload, 5); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -86,10 +85,9 @@ export class ExtNVMWriteLongBufferRequest extends Message { @messageTypes(MessageType.Response, FunctionType.ExtNVMWriteLongBuffer) export class ExtNVMWriteLongBufferResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts index c47e36afd05d..b645d01ecefb 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts @@ -4,12 +4,12 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, MessageType, expectedResponse, gotDeserializationOptions, @@ -28,12 +28,11 @@ export interface ExtNVMWriteLongByteRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ExtExtWriteLongByte) export class ExtNVMWriteLongByteRequest extends Message { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtNVMWriteLongByteRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -60,11 +59,11 @@ export class ExtNVMWriteLongByteRequest extends Message { public offset: number; public byte: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(4); this.payload.writeUIntBE(this.offset, 0, 3); this.payload[3] = this.byte; - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -81,10 +80,9 @@ export class ExtNVMWriteLongByteRequest extends Message { @messageTypes(MessageType.Response, FunctionType.ExtExtWriteLongByte) export class ExtNVMWriteLongByteResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.success = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts index e8fac7aa425c..4bfd2177595b 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts @@ -6,8 +6,10 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -45,13 +47,13 @@ export class ExtendedNVMOperationsRequest extends Message { // This must be set in subclasses public command!: ExtendedNVMOperationsCommand; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.command]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -73,8 +75,8 @@ export class ExtendedNVMOperationsRequest extends Message { export class ExtendedNVMOperationsOpenRequest extends ExtendedNVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = ExtendedNVMOperationsCommand.Open; } } @@ -84,8 +86,8 @@ export class ExtendedNVMOperationsOpenRequest export class ExtendedNVMOperationsCloseRequest extends ExtendedNVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = ExtendedNVMOperationsCommand.Close; } } @@ -103,12 +105,11 @@ export class ExtendedNVMOperationsReadRequest extends ExtendedNVMOperationsRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtendedNVMOperationsReadRequestOptions, ) { - super(host, options); + super(options); this.command = ExtendedNVMOperationsCommand.Read; if (gotDeserializationOptions(options)) { @@ -138,12 +139,12 @@ export class ExtendedNVMOperationsReadRequest public length: number; public offset: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(5); this.payload[0] = this.length; this.payload.writeUInt32BE(this.offset, 1); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -172,12 +173,11 @@ export class ExtendedNVMOperationsWriteRequest extends ExtendedNVMOperationsRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | ExtendedNVMOperationsWriteRequestOptions, ) { - super(host, options); + super(options); this.command = ExtendedNVMOperationsCommand.Write; if (gotDeserializationOptions(options)) { @@ -207,12 +207,12 @@ export class ExtendedNVMOperationsWriteRequest public offset: number; public buffer: Buffer; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(1 + 4 + this.buffer.length); this.payload[0] = this.buffer.length; this.payload.writeUInt32BE(this.offset, 1); this.buffer.copy(this.payload, 5); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -237,10 +237,9 @@ export class ExtendedNVMOperationsResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.status = this.payload[0]; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts index 0c1d229373bd..874d40ce710d 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts @@ -7,10 +7,10 @@ import { createSimpleReflectionDecorator, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import type { DeserializingMessageConstructor, MessageBaseOptions, + MessageEncodingContext, } from "@zwave-js/serial"; import { FunctionType, @@ -70,8 +70,8 @@ function testResponseForFirmwareUpdateNVMRequest( @priority(MessagePriority.Controller) @expectedResponse(testResponseForFirmwareUpdateNVMRequest) export class FirmwareUpdateNVMRequest extends Message { - public constructor(host: ZWaveHost, options: MessageOptions = {}) { - super(host, options); + public constructor(options: MessageOptions = {}) { + super(options); if (gotDeserializationOptions(options)) { throw new ZWaveError( `${this.constructor.name}: deserialization not implemented`, @@ -84,13 +84,13 @@ export class FirmwareUpdateNVMRequest extends Message { public command: FirmwareUpdateNVMCommand; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.command]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -110,17 +110,16 @@ export class FirmwareUpdateNVMRequest extends Message { @messageTypes(MessageType.Response, FunctionType.FirmwareUpdateNVM) export class FirmwareUpdateNVMResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.command = this.payload[0]; const CommandConstructor = getSubCommandResponseConstructor( this.command, ); if (CommandConstructor && (new.target as any) !== CommandConstructor) { - return new CommandConstructor(host, options); + return new CommandConstructor(options); } this.payload = this.payload.subarray(1); @@ -150,10 +149,9 @@ export class FirmwareUpdateNVM_InitRequest extends FirmwareUpdateNVMRequest {} @subCommandResponse(FirmwareUpdateNVMCommand.Init) export class FirmwareUpdateNVM_InitResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.supported = this.payload[0] !== 0; } @@ -181,12 +179,11 @@ export class FirmwareUpdateNVM_SetNewImageRequest extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | FirmwareUpdateNVM_SetNewImageRequestOptions, ) { - super(host, options); + super(options); this.command = FirmwareUpdateNVMCommand.SetNewImage; if (gotDeserializationOptions(options)) { @@ -201,10 +198,10 @@ export class FirmwareUpdateNVM_SetNewImageRequest public newImage: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.newImage ? 1 : 0]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -221,10 +218,9 @@ export class FirmwareUpdateNVM_SetNewImageResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.changed = this.payload[0] !== 0; } @@ -251,10 +247,9 @@ export class FirmwareUpdateNVM_GetNewImageResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.newImage = this.payload[0] !== 0; } @@ -284,12 +279,11 @@ export class FirmwareUpdateNVM_UpdateCRC16Request extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | FirmwareUpdateNVM_UpdateCRC16RequestOptions, ) { - super(host, options); + super(options); this.command = FirmwareUpdateNVMCommand.UpdateCRC16; if (gotDeserializationOptions(options)) { @@ -313,13 +307,13 @@ export class FirmwareUpdateNVM_UpdateCRC16Request return 30000; } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(7); this.payload.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.blockLength, 3); this.payload.writeUInt16BE(this.crcSeed, 5); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -338,10 +332,9 @@ export class FirmwareUpdateNVM_UpdateCRC16Response extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.crc16 = this.payload.readUint16BE(0); } @@ -374,10 +367,9 @@ export class FirmwareUpdateNVM_IsValidCRC16Response extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.isValid = this.payload[0] !== 0; // There are two more bytes containing the CRC result, but we don't care about that } @@ -405,12 +397,11 @@ export interface FirmwareUpdateNVM_WriteRequestOptions @subCommandRequest(FirmwareUpdateNVMCommand.Write) export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | FirmwareUpdateNVM_WriteRequestOptions, ) { - super(host, options); + super(options); this.command = FirmwareUpdateNVMCommand.Write; if (gotDeserializationOptions(options)) { @@ -427,12 +418,12 @@ export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { public offset: number; public buffer: Buffer; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([Buffer.allocUnsafe(5), this.buffer]); this.payload.writeUintBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.buffer.length, 3); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -452,10 +443,9 @@ export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { @subCommandResponse(FirmwareUpdateNVMCommand.Write) export class FirmwareUpdateNVM_WriteResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.overwritten = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts index 8dfa52d06847..32fbe189dd50 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts @@ -1,5 +1,4 @@ import { type MessageOrCCLogEntry, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, @@ -74,10 +73,9 @@ export class GetNVMIdRequest extends Message {} @messageTypes(MessageType.Response, FunctionType.GetNVMId) export class GetNVMIdResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.nvmManufacturerId = this.payload[1]; this.memoryType = this.payload[2]; this.memorySize = this.payload[3]; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts index 5adc036411f9..0b8854f803aa 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts @@ -6,15 +6,15 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, + type SuccessIndicator, expectedResponse, gotDeserializationOptions, messageTypes, @@ -44,13 +44,13 @@ export class NVMOperationsRequest extends Message { // This must be set in subclasses public command!: NVMOperationsCommand; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.concat([ Buffer.from([this.command]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -67,8 +67,8 @@ export class NVMOperationsRequest extends Message { // ============================================================================= export class NVMOperationsOpenRequest extends NVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = NVMOperationsCommand.Open; } } @@ -76,8 +76,8 @@ export class NVMOperationsOpenRequest extends NVMOperationsRequest { // ============================================================================= export class NVMOperationsCloseRequest extends NVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options?: MessageOptions) { + super(options); this.command = NVMOperationsCommand.Close; } } @@ -91,12 +91,11 @@ export interface NVMOperationsReadRequestOptions extends MessageBaseOptions { export class NVMOperationsReadRequest extends NVMOperationsRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | NVMOperationsReadRequestOptions, ) { - super(host, options); + super(options); this.command = NVMOperationsCommand.Read; if (gotDeserializationOptions(options)) { @@ -126,12 +125,12 @@ export class NVMOperationsReadRequest extends NVMOperationsRequest { public length: number; public offset: number; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(3); this.payload[0] = this.length; this.payload.writeUInt16BE(this.offset, 1); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -156,12 +155,11 @@ export interface NVMOperationsWriteRequestOptions extends MessageBaseOptions { export class NVMOperationsWriteRequest extends NVMOperationsRequest { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | NVMOperationsWriteRequestOptions, ) { - super(host, options); + super(options); this.command = NVMOperationsCommand.Write; if (gotDeserializationOptions(options)) { @@ -191,12 +189,12 @@ export class NVMOperationsWriteRequest extends NVMOperationsRequest { public offset: number; public buffer: Buffer; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(3 + this.buffer.length); this.payload[0] = this.buffer.length; this.payload.writeUInt16BE(this.offset, 1); this.buffer.copy(this.payload, 3); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -219,10 +217,9 @@ export class NVMOperationsWriteRequest extends NVMOperationsRequest { @messageTypes(MessageType.Response, FunctionType.NVMOperations) export class NVMOperationsResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); validatePayload(this.payload.length >= 2); this.status = this.payload[0]; diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts index 94277d88cae2..d26e049f9cc1 100644 --- a/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts @@ -12,8 +12,11 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { CCEncodingContext } from "@zwave-js/host"; +import type { + MessageEncodingContext, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, @@ -37,14 +40,14 @@ import { parseTXReport, txReportToMessageRecord } from "./SendDataShared"; @messageTypes(MessageType.Request, FunctionType.SendDataBridge) @priority(MessagePriority.Normal) export class SendDataBridgeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== SendDataBridgeRequestTransmitReport ) { - return new SendDataBridgeRequestTransmitReport(host, options); + return new SendDataBridgeRequestTransmitReport(options); } - super(host, options); + super(options); } } @@ -52,7 +55,7 @@ interface SendDataBridgeRequestOptions< CCType extends CommandClass = CommandClass, > extends MessageBaseOptions { command: CCType; - sourceNodeId?: number; + sourceNodeId: number; transmitOptions?: TransmitOptions; maxSendAttempts?: number; } @@ -64,10 +67,9 @@ export class SendDataBridgeRequest implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: SendDataBridgeRequestOptions, ) { - super(host, options); + super(options); if (!options.command.isSinglecast() && !options.command.isBroadcast()) { throw new ZWaveError( @@ -76,7 +78,7 @@ export class SendDataBridgeRequest ); } - this.sourceNodeId = options.sourceNodeId ?? host.ownNodeId; + this.sourceNodeId = options.sourceNodeId; this.command = options.command; this.transmitOptions = options.transmitOptions @@ -110,9 +112,9 @@ export class SendDataBridgeRequest // Cache the serialized CC, so we can check if it needs to be fragmented private _serializedCC: Buffer | undefined; /** @internal */ - public serializeCC(): Buffer { + public serializeCC(ctx: CCEncodingContext): Buffer { if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); + this._serializedCC = this.command.serialize(ctx); } return this._serializedCC; } @@ -123,16 +125,17 @@ export class SendDataBridgeRequest this.callbackId = undefined; } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); const sourceNodeId = encodeNodeID( this.sourceNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); const destinationNodeId = encodeNodeID( this.command.nodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); - const serializedCC = this.serializeCC(); + const serializedCC = this.serializeCC(ctx); this.payload = Buffer.concat([ sourceNodeId, @@ -142,7 +145,7 @@ export class SendDataBridgeRequest Buffer.from([this.transmitOptions, 0, 0, 0, 0, this.callbackId]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -151,7 +154,7 @@ export class SendDataBridgeRequest message: { "source node id": this.sourceNodeId, "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -186,12 +189,11 @@ export class SendDataBridgeRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataBridgeRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -218,7 +220,7 @@ export class SendDataBridgeRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName(TransmitStatus, this.transmitStatus) + (this.txReport @@ -237,10 +239,9 @@ export class SendDataBridgeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._wasSent = this.payload[0] !== 0; } @@ -264,18 +265,15 @@ export class SendDataBridgeResponse extends Message @messageTypes(MessageType.Request, FunctionType.SendDataMulticastBridge) @priority(MessagePriority.Normal) export class SendDataMulticastBridgeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== SendDataMulticastBridgeRequestTransmitReport ) { - return new SendDataMulticastBridgeRequestTransmitReport( - host, - options, - ); + return new SendDataMulticastBridgeRequestTransmitReport(options); } - super(host, options); + super(options); } } @@ -283,7 +281,7 @@ interface SendDataMulticastBridgeRequestOptions extends MessageBaseOptions { command: CCType; - sourceNodeId?: number; + sourceNodeId: number; transmitOptions?: TransmitOptions; maxSendAttempts?: number; } @@ -294,10 +292,9 @@ export class SendDataMulticastBridgeRequest< CCType extends CommandClass = CommandClass, > extends SendDataMulticastBridgeRequestBase implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: SendDataMulticastBridgeRequestOptions, ) { - super(host, options); + super(options); if (!options.command.isMulticast()) { throw new ZWaveError( @@ -316,7 +313,7 @@ export class SendDataMulticastBridgeRequest< ); } - this.sourceNodeId = options.sourceNodeId ?? host.ownNodeId; + this.sourceNodeId = options.sourceNodeId; this.command = options.command; this.transmitOptions = options.transmitOptions ?? TransmitOptions.DEFAULT; @@ -350,9 +347,9 @@ export class SendDataMulticastBridgeRequest< // Cache the serialized CC, so we can check if it needs to be fragmented private _serializedCC: Buffer | undefined; /** @internal */ - public serializeCC(): Buffer { + public serializeCC(ctx: CCEncodingContext): Buffer { if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); + this._serializedCC = this.command.serialize(ctx); } return this._serializedCC; } @@ -363,14 +360,15 @@ export class SendDataMulticastBridgeRequest< this.callbackId = undefined; } - public serialize(): Buffer { - const serializedCC = this.serializeCC(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const serializedCC = this.serializeCC(ctx); const sourceNodeId = encodeNodeID( this.sourceNodeId, - this.host.nodeIdType, + ctx.nodeIdType, ); const destinationNodeIDs = this.command.nodeId.map((id) => - encodeNodeID(id, this.host.nodeIdType) + encodeNodeID(id, ctx.nodeIdType) ); this.payload = Buffer.concat([ @@ -384,7 +382,7 @@ export class SendDataMulticastBridgeRequest< Buffer.from([this.transmitOptions, this.callbackId]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -394,7 +392,7 @@ export class SendDataMulticastBridgeRequest< "source node id": this.sourceNodeId, "target nodes": this.command.nodeId.join(", "), "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -412,12 +410,11 @@ export class SendDataMulticastBridgeRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataMulticastBridgeRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -441,7 +438,7 @@ export class SendDataMulticastBridgeRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, @@ -456,10 +453,9 @@ export class SendDataMulticastBridgeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this._wasSent = this.payload[0] !== 0; } diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts index f2f7f169a4ec..7023067ab38f 100644 --- a/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts @@ -5,6 +5,7 @@ import { MessagePriority, type MulticastCC, type MulticastDestination, + NODE_ID_BROADCAST, type SerializableTXReport, type SinglecastCC, type TXReport, @@ -15,12 +16,13 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; +import type { CCEncodingContext } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageOrigin, MessageType, @@ -46,21 +48,21 @@ export const MAX_SEND_ATTEMPTS = 5; @messageTypes(MessageType.Request, FunctionType.SendData) @priority(MessagePriority.Normal) export class SendDataRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if (gotDeserializationOptions(options)) { if ( options.origin === MessageOrigin.Host && (new.target as any) !== SendDataRequest ) { - return new SendDataRequest(host, options); + return new SendDataRequest(options); } else if ( options.origin !== MessageOrigin.Host && (new.target as any) !== SendDataRequestTransmitReport ) { - return new SendDataRequestTransmitReport(host, options); + return new SendDataRequestTransmitReport(options); } } - super(host, options); + super(options); } } @@ -79,16 +81,15 @@ export class SendDataRequest implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SendDataRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { let offset = 0; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += nodeIdBytes; @@ -103,10 +104,14 @@ export class SendDataRequest ); if (options.parseCCs !== false) { - this.command = CommandClass.from(host, { + this.command = CommandClass.from({ nodeId, data: this.payload, origin: options.origin, + context: { + sourceNodeId: nodeId, + ...options.ctx, + }, }) as SinglecastCC; } else { // Little hack for testing with a network mock. This will be parsed in the next step. @@ -155,9 +160,9 @@ export class SendDataRequest // Cache the serialized CC, so we can check if it needs to be fragmented private _serializedCC: Buffer | undefined; /** @internal */ - public serializeCC(): Buffer { + public serializeCC(ctx: CCEncodingContext): Buffer { if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); + this._serializedCC = this.command.serialize(ctx); } return this._serializedCC; } @@ -168,9 +173,10 @@ export class SendDataRequest this.callbackId = undefined; } - public serialize(): Buffer { - const nodeId = encodeNodeID(this.command.nodeId, this.host.nodeIdType); - const serializedCC = this.serializeCC(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.command.nodeId, ctx.nodeIdType); + const serializedCC = this.serializeCC(ctx); this.payload = Buffer.concat([ nodeId, Buffer.from([serializedCC.length]), @@ -178,7 +184,7 @@ export class SendDataRequest Buffer.from([this.transmitOptions, this.callbackId]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -186,7 +192,7 @@ export class SendDataRequest ...super.toLogEntry(), message: { "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -219,12 +225,11 @@ export class SendDataRequestTransmitReport extends SendDataRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -245,7 +250,8 @@ export class SendDataRequestTransmitReport extends SendDataRequestBase private _txReport: SerializableTXReport | undefined; public txReport: TXReport | undefined; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([ this.callbackId, this.transmitStatus, @@ -257,7 +263,7 @@ export class SendDataRequestTransmitReport extends SendDataRequestBase ]); } - return super.serialize(); + return super.serialize(ctx); } public isOK(): boolean { @@ -268,7 +274,7 @@ export class SendDataRequestTransmitReport extends SendDataRequestBase return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName(TransmitStatus, this.transmitStatus) + (this.txReport @@ -289,10 +295,9 @@ export interface SendDataResponseOptions extends MessageBaseOptions { @messageTypes(MessageType.Response, FunctionType.SendData) export class SendDataResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SendDataResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wasSent = this.payload[0] !== 0; } else { @@ -302,9 +307,9 @@ export class SendDataResponse extends Message implements SuccessIndicator { public wasSent: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.wasSent ? 1 : 0]); - return super.serialize(); + return super.serialize(ctx); } isOK(): boolean { @@ -322,26 +327,23 @@ export class SendDataResponse extends Message implements SuccessIndicator { @messageTypes(MessageType.Request, FunctionType.SendDataMulticast) @priority(MessagePriority.Normal) export class SendDataMulticastRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if (gotDeserializationOptions(options)) { if ( options.origin === MessageOrigin.Host && (new.target as any) !== SendDataMulticastRequest ) { - return new SendDataMulticastRequest(host, options); + return new SendDataMulticastRequest(options); } else if ( options.origin !== MessageOrigin.Host && (new.target as any) !== SendDataMulticastRequestTransmitReport ) { - return new SendDataMulticastRequestTransmitReport( - host, - options, - ); + return new SendDataMulticastRequestTransmitReport(options); } } - super(host, options); + super(options); } } @@ -359,12 +361,11 @@ export class SendDataMulticastRequest< CCType extends CommandClass = CommandClass, > extends SendDataMulticastRequestBase implements ICommandClassContainer { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataMulticastRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { const numNodeIDs = this.payload[0]; @@ -373,7 +374,7 @@ export class SendDataMulticastRequest< for (let i = 0; i < numNodeIDs; i++) { const { nodeId, bytesRead } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); nodeIds.push(nodeId); @@ -395,10 +396,14 @@ export class SendDataMulticastRequest< this.payload = serializedCC; if (options.parseCCs !== false) { - this.command = CommandClass.from(host, { + this.command = CommandClass.from({ nodeId: this._nodeIds[0], data: this.payload, origin: options.origin, + context: { + sourceNodeId: NODE_ID_BROADCAST, // FIXME: Unknown? + ...options.ctx, + }, }) as MulticastCC; this.command.nodeId = this._nodeIds; } else { @@ -457,9 +462,9 @@ export class SendDataMulticastRequest< // Cache the serialized CC, so we can check if it needs to be fragmented private _serializedCC: Buffer | undefined; /** @internal */ - public serializeCC(): Buffer { + public serializeCC(ctx: CCEncodingContext): Buffer { if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); + this._serializedCC = this.command.serialize(ctx); } return this._serializedCC; } @@ -470,10 +475,11 @@ export class SendDataMulticastRequest< this.callbackId = undefined; } - public serialize(): Buffer { - const serializedCC = this.serializeCC(); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const serializedCC = this.serializeCC(ctx); const destinationNodeIDs = this.command.nodeId.map((id) => - encodeNodeID(id, this.host.nodeIdType) + encodeNodeID(id, ctx.nodeIdType) ); this.payload = Buffer.concat([ // # of target nodes, not # of bytes @@ -485,7 +491,7 @@ export class SendDataMulticastRequest< Buffer.from([this.transmitOptions, this.callbackId]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -494,7 +500,7 @@ export class SendDataMulticastRequest< message: { "target nodes": this.command.nodeId.join(", "), "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -512,12 +518,11 @@ export class SendDataMulticastRequestTransmitReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataMulticastRequestTransmitReportOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.callbackId = this.payload[0]; @@ -534,9 +539,10 @@ export class SendDataMulticastRequestTransmitReport return this._transmitStatus; } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId, this._transmitStatus]); - return super.serialize(); + return super.serialize(ctx); } public isOK(): boolean { @@ -547,7 +553,7 @@ export class SendDataMulticastRequestTransmitReport return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, @@ -566,12 +572,11 @@ export class SendDataMulticastResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, options: | MessageDeserializationOptions | SendDataMulticastResponseOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { this.wasSent = this.payload[0] !== 0; } else { @@ -581,9 +586,9 @@ export class SendDataMulticastResponse extends Message public wasSent: boolean; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.from([this.wasSent ? 1 : 0]); - return super.serialize(); + return super.serialize(ctx); } public isOK(): boolean { diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts index feea59a1c3e1..9224515076ea 100644 --- a/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts @@ -6,12 +6,12 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, type MessageDeserializationOptions, + type MessageEncodingContext, type MessageOptions, MessageType, type SuccessIndicator, @@ -26,14 +26,14 @@ import { getEnumMemberName } from "@zwave-js/shared"; @messageTypes(MessageType.Request, FunctionType.SendTestFrame) @priority(MessagePriority.Normal) export class SendTestFrameRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { + public constructor(options: MessageOptions) { if ( gotDeserializationOptions(options) && (new.target as any) !== SendTestFrameTransmitReport ) { - return new SendTestFrameTransmitReport(host, options); + return new SendTestFrameTransmitReport(options); } - super(host, options); + super(options); } } @@ -46,15 +46,14 @@ export interface SendTestFrameRequestOptions extends MessageBaseOptions { @expectedCallback(FunctionType.SendTestFrame) export class SendTestFrameRequest extends SendTestFrameRequestBase { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions | SendTestFrameRequestOptions, ) { - super(host, options); + super(options); if (gotDeserializationOptions(options)) { let offset = 0; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( this.payload, - host.nodeIdType, + options.ctx.nodeIdType, offset, ); offset += nodeIdBytes; @@ -71,8 +70,9 @@ export class SendTestFrameRequest extends SendTestFrameRequestBase { public testNodeId: number; public powerlevel: Powerlevel; - public serialize(): Buffer { - const nodeId = encodeNodeID(this.testNodeId, this.host.nodeIdType); + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.testNodeId, ctx.nodeIdType); this.payload = Buffer.concat([ nodeId, Buffer.from([ @@ -81,7 +81,7 @@ export class SendTestFrameRequest extends SendTestFrameRequestBase { ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -90,7 +90,7 @@ export class SendTestFrameRequest extends SendTestFrameRequestBase { message: { "test node id": this.testNodeId, powerlevel: getEnumMemberName(Powerlevel, this.powerlevel), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } @@ -99,10 +99,9 @@ export class SendTestFrameRequest extends SendTestFrameRequestBase { @messageTypes(MessageType.Response, FunctionType.SendTestFrame) export class SendTestFrameResponse extends Message { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.wasSent = this.payload[0] !== 0; } @@ -120,10 +119,9 @@ export class SendTestFrameTransmitReport extends SendTestFrameRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, options: MessageDeserializationOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; @@ -139,7 +137,7 @@ export class SendTestFrameTransmitReport extends SendTestFrameRequestBase return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "transmit status": getEnumMemberName( TransmitStatus, this.transmitStatus, diff --git a/packages/zwave-js/src/lib/telemetry/deviceConfig.ts b/packages/zwave-js/src/lib/telemetry/deviceConfig.ts index 898afa2c07e0..bb501b3be2f2 100644 --- a/packages/zwave-js/src/lib/telemetry/deviceConfig.ts +++ b/packages/zwave-js/src/lib/telemetry/deviceConfig.ts @@ -1,7 +1,6 @@ // import got from "@esm2cjs/got"; // import { AssociationGroupInfoCC, ConfigurationCC } from "@zwave-js/cc"; // import { CommandClasses } from "@zwave-js/core"; -import type { ZWaveApplicationHost } from "@zwave-js/host"; // import { formatId } from "@zwave-js/shared"; // import { isObject } from "alcalzone-shared/typeguards"; import type { ZWaveNode } from "../node/Node"; @@ -9,7 +8,7 @@ import type { ZWaveNode } from "../node/Node"; // const missingDeviceConfigCache = new Set(); export async function reportMissingDeviceConfig( - _applHost: ZWaveApplicationHost, + _ctx: any, _node: ZWaveNode & { manufacturerId: number; productType: number; diff --git a/packages/zwave-js/src/lib/test/cc-specific/discardUnsupportedReports.test.ts b/packages/zwave-js/src/lib/test/cc-specific/discardUnsupportedReports.test.ts index e1eecc15007a..cbd179e574c3 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/discardUnsupportedReports.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/discardUnsupportedReports.test.ts @@ -24,8 +24,8 @@ integrationTest( testBody: async (t, driver, node, mockController, mockNode) => { // Unsupported report from root endpoint - let cc: CommandClass = new MultilevelSensorCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc: CommandClass = new MultilevelSensorCCReport({ + nodeId: mockController.ownNodeId, type: 0x01, // Temperature scale: 0x00, // Celsius value: 1.001, @@ -37,14 +37,14 @@ integrationTest( ); // Report from endpoint 1, unsupported on root - cc = new MultilevelSensorCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MultilevelSensorCCReport({ + nodeId: mockController.ownNodeId, type: 0x01, // Temperature scale: 0x00, // Celsius value: 25.12, }); - cc = new MultiChannelCCCommandEncapsulation(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MultiChannelCCCommandEncapsulation({ + nodeId: mockController.ownNodeId, endpoint: 1, destination: 0, encapsulated: cc, @@ -56,14 +56,14 @@ integrationTest( ); // Unsupported Report from endpoint 1, supported on root - cc = new MeterCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MeterCCReport({ + nodeId: mockController.ownNodeId, type: 0x01, // Electric scale: 0x00, // kWh value: 1.234, }); - cc = new MultiChannelCCCommandEncapsulation(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MultiChannelCCCommandEncapsulation({ + nodeId: mockController.ownNodeId, endpoint: 1, destination: 0, encapsulated: cc, @@ -75,8 +75,8 @@ integrationTest( ); // Supported report from root endpoint - cc = new MeterCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MeterCCReport({ + nodeId: mockController.ownNodeId, type: 0x01, // Electric scale: 0x00, // kWh value: 2.34, diff --git a/packages/zwave-js/src/lib/test/cc-specific/mapNotificationDoorLock.test.ts b/packages/zwave-js/src/lib/test/cc-specific/mapNotificationDoorLock.test.ts index 2828c1904ac6..b99c1977ba42 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/mapNotificationDoorLock.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/mapNotificationDoorLock.test.ts @@ -18,8 +18,8 @@ integrationTest( testBody: async (t, driver, node, mockController, mockNode) => { const valueId = DoorLockCCValues.currentMode.id; - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control, notificationEvent: 0x01, // Manual Lock Operation }); @@ -33,8 +33,8 @@ integrationTest( t.is(node.getValue(valueId), DoorLockMode.Secured); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control, notificationEvent: 0x06, // Keypad Unlock Operation }); diff --git a/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts b/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts index 6a5f2407b92f..3c935c56b20a 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts @@ -49,8 +49,8 @@ integrationTest( }); // Send notifications to the node - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x0f, notificationEvent: 0x01, eventParameters: Buffer.from([0x00]), // Off / Closed @@ -66,8 +66,8 @@ integrationTest( let value = node.getValue(valveOperationStatusId); t.is(value, 0x00); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x0f, notificationEvent: 0x01, eventParameters: Buffer.from([0x01]), // On / Open @@ -124,8 +124,8 @@ integrationTest( }); // Send notifications to the node - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, eventParameters: Buffer.from([0x00]), // open in regular position @@ -141,8 +141,8 @@ integrationTest( let value = node.getValue(doorStateValueId); t.is(value, 0x1600); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, eventParameters: Buffer.from([0x01]), // open in tilt position @@ -157,8 +157,8 @@ integrationTest( value = node.getValue(doorStateValueId); t.is(value, 0x1601); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // open }); @@ -172,8 +172,8 @@ integrationTest( value = node.getValue(doorStateValueId); t.is(value, 0x16); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x17, // closed }); @@ -240,8 +240,8 @@ integrationTest("The 'simple' Door state value works correctly", { ); const valueSimple = NotificationCCValues.doorStateSimple; - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x01]), // ... in tilt position @@ -259,8 +259,8 @@ integrationTest("The 'simple' Door state value works correctly", { // === - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x00]), // ... in regular position @@ -278,8 +278,8 @@ integrationTest("The 'simple' Door state value works correctly", { // === - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open }); @@ -296,8 +296,8 @@ integrationTest("The 'simple' Door state value works correctly", { // === - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x17, // Window/door is closed }); @@ -346,8 +346,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { t.false(hasTiltVID()); // Send a notification to the node where the window is not tilted - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x00]), // ... in regular position @@ -366,8 +366,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // Again with tilt - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x01]), // ... in tilt position @@ -387,8 +387,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // Again without tilt - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x00]), // ... in regular position @@ -406,8 +406,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // Again with tilt to be able to detect changes - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x01]), // ... in tilt position @@ -425,8 +425,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // And now without the enum - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x17, // Window/door is closed }); @@ -443,8 +443,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // Again with tilt to be able to detect changes - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open eventParameters: Buffer.from([0x01]), // ... in tilt position @@ -462,8 +462,8 @@ integrationTest("The synthetic 'Door tilt state' value works correctly", { // === // And again without the enum - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, notificationEvent: 0x16, // Window/door is open }); @@ -520,8 +520,8 @@ integrationTest( }); // Send notifications to the node - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x05, notificationEvent: 0x07, eventParameters: Buffer.from([0x02]), // Below low threshold @@ -538,8 +538,8 @@ integrationTest( t.is(value, 0x02); // Now send one without an event parameter - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x05, notificationEvent: 0x07, }); @@ -553,8 +553,8 @@ integrationTest( value = node.getValue(waterPressureAlarmValueId); t.is(value, 0x01); - // cc = new NotificationCCReport(mockNode.host, { - // nodeId: mockController.host.ownNodeId, + // cc = new NotificationCCReport({ + // nodeId: mockController.ownNodeId, // notificationType: 0x06, // notificationEvent: 0x16, // open // }); @@ -568,8 +568,8 @@ integrationTest( // value = node.getValue(waterPressureAlarmValueId); // t.is(value, 0x16); - // cc = new NotificationCCReport(mockNode.host, { - // nodeId: mockController.host.ownNodeId, + // cc = new NotificationCCReport({ + // nodeId: mockController.ownNodeId, // notificationType: 0x06, // notificationEvent: 0x17, // closed // }); diff --git a/packages/zwave-js/src/lib/test/cc-specific/notificationIdleManually.test.ts b/packages/zwave-js/src/lib/test/cc-specific/notificationIdleManually.test.ts index 27bb94b6bfd1..736216c9a9c3 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/notificationIdleManually.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/notificationIdleManually.test.ts @@ -17,8 +17,8 @@ integrationTest("Notification values can get idled manually", { "Alarm status", ).id; - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x01, // Smoke Alarm notificationEvent: 0x03, // Smoke alarm test }); @@ -46,8 +46,8 @@ integrationTest("Notification values can get idled manually", { "Door state", ).id; - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control notificationEvent: 0x16, // Door state }); @@ -80,8 +80,8 @@ integrationTest( "Alarm status", ).id; - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x01, // Smoke Alarm notificationEvent: 0x03, // Smoke alarm test }); @@ -108,8 +108,8 @@ integrationTest( "Door state", ).id; - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control notificationEvent: 0x16, // Door state }); diff --git a/packages/zwave-js/src/lib/test/cc-specific/notificationIdleRelated.test.ts b/packages/zwave-js/src/lib/test/cc-specific/notificationIdleRelated.test.ts index 31e5fc7f94fb..ddc52c574833 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/notificationIdleRelated.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/notificationIdleRelated.test.ts @@ -22,8 +22,8 @@ integrationTest( "Lock state", ).id; - let cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control, notificationEvent: 0x0b, // Lock jammed }); @@ -37,8 +37,8 @@ integrationTest( t.is(node.getValue(lockStateValueId), 0x0b /* Lock jammed */); - cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control, notificationEvent: 0x06, // Keypad Unlock Operation }); diff --git a/packages/zwave-js/src/lib/test/cc-specific/undefinedTargetValue.test.ts b/packages/zwave-js/src/lib/test/cc-specific/undefinedTargetValue.test.ts index 77b2cabd358b..e1ed5ddb699f 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/undefinedTargetValue.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/undefinedTargetValue.test.ts @@ -18,8 +18,8 @@ integrationTest( const targetValueValueID = BinarySwitchCCValues.targetValue.id; node.valueDB.setValue(targetValueValueID, false); - const cc = new BinarySwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: true, }); await mockNode.sendToController( diff --git a/packages/zwave-js/src/lib/test/cc-specific/unknownNotifications.test.ts b/packages/zwave-js/src/lib/test/cc-specific/unknownNotifications.test.ts index 82c6a4857b7b..0cc050765dc9 100644 --- a/packages/zwave-js/src/lib/test/cc-specific/unknownNotifications.test.ts +++ b/packages/zwave-js/src/lib/test/cc-specific/unknownNotifications.test.ts @@ -18,8 +18,8 @@ integrationTest( ), testBody: async (t, driver, node, mockController, mockNode) => { - const cc = new NotificationCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new NotificationCCReport({ + nodeId: mockController.ownNodeId, notificationType: 0x06, // Access Control, notificationEvent: 0xfd, // Manual Lock Operation }); diff --git a/packages/zwave-js/src/lib/test/cc/API.test.ts b/packages/zwave-js/src/lib/test/cc/API.test.ts index 97762861ac6e..5770a3b76845 100644 --- a/packages/zwave-js/src/lib/test/cc/API.test.ts +++ b/packages/zwave-js/src/lib/test/cc/API.test.ts @@ -51,6 +51,6 @@ test.after.always(async (t) => { test.serial(`supportsCommand() returns NOT_KNOWN by default`, (t) => { const { node2, driver } = t.context; - const API = new DummyCCAPI(driver, node2); + const API = new DummyCCAPI(node2); t.is(API.supportsCommand(null as any), NOT_KNOWN); }); diff --git a/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts b/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts index 2d272ee5865f..06a44672522f 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts @@ -8,11 +8,8 @@ import { AssociationCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -23,7 +20,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the SupportedGroupingsGet command should serialize correctly", (t) => { - const cc = new AssociationCCSupportedGroupingsGet(host, { + const cc = new AssociationCCSupportedGroupingsGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -31,7 +28,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { AssociationCommand.SupportedGroupingsGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedGroupingsReport command should be deserialized correctly", (t) => { @@ -41,16 +38,17 @@ test("the SupportedGroupingsReport command should be deserialized correctly", (t 7, // # of groups ]), ); - const cc = new AssociationCCSupportedGroupingsReport(host, { + const cc = new AssociationCCSupportedGroupingsReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.groupCount, 7); }); test("the Set command should serialize correctly", (t) => { - const cc = new AssociationCCSet(host, { + const cc = new AssociationCCSet({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 5], @@ -65,10 +63,10 @@ test("the Set command should serialize correctly", (t) => { 5, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Get command should serialize correctly", (t) => { - const cc = new AssociationCCGet(host, { + const cc = new AssociationCCGet({ nodeId: 1, groupId: 9, }); @@ -78,7 +76,7 @@ test("the Get command should serialize correctly", (t) => { 9, // group ID ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly", (t) => { @@ -94,9 +92,10 @@ test("the Report command should be deserialized correctly", (t) => { 5, ]), ); - const cc = new AssociationCCReport(host, { + const cc = new AssociationCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.groupId, 5); @@ -106,7 +105,7 @@ test("the Report command should be deserialized correctly", (t) => { }); test("the Remove command should serialize correctly", (t) => { - const cc = new AssociationCCRemove(host, { + const cc = new AssociationCCRemove({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 5], @@ -121,11 +120,11 @@ test("the Remove command should serialize correctly", (t) => { 5, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Remove command should serialize correctly (empty node list)", (t) => { - const cc = new AssociationCCRemove(host, { + const cc = new AssociationCCRemove({ nodeId: 2, groupId: 5, }); @@ -135,7 +134,7 @@ test("the Remove command should serialize correctly (empty node list)", (t) => { 5, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); // test("deserializing an unsupported command should return an unspecified version of AssociationCC", (t) => { @@ -143,7 +142,7 @@ test("the Remove command should serialize correctly (empty node list)", (t) => { // 1, // Buffer.from([255]), // not a valid command // ); -// const cc: any = new AssociationCC(host, { +// const cc: any = new AssociationCC({ // data: serializedCC, // }); // t.is(cc.constructor, AssociationCC); diff --git a/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts b/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts index d50418b49435..0bf177f9dda6 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts @@ -11,11 +11,8 @@ import { BasicCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -26,7 +23,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the NameGet command should serialize correctly", (t) => { - const cc = new AssociationGroupInfoCCNameGet(host, { + const cc = new AssociationGroupInfoCCNameGet({ nodeId: 1, groupId: 7, }); @@ -36,7 +33,7 @@ test("the NameGet command should serialize correctly", (t) => { 7, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the NameReport command should be deserialized correctly", (t) => { @@ -54,9 +51,10 @@ test("the NameReport command should be deserialized correctly", (t) => { 0x72, ]), ); - const cc = new AssociationGroupInfoCCNameReport(host, { + const cc = new AssociationGroupInfoCCNameReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.groupId, 7); @@ -64,7 +62,7 @@ test("the NameReport command should be deserialized correctly", (t) => { }); test("the InfoGet command should serialize correctly (no flag set)", (t) => { - const cc = new AssociationGroupInfoCCInfoGet(host, { + const cc = new AssociationGroupInfoCCInfoGet({ nodeId: 1, groupId: 7, listMode: false, @@ -77,11 +75,11 @@ test("the InfoGet command should serialize correctly (no flag set)", (t) => { 7, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the InfoGet command should serialize correctly (refresh cache flag set)", (t) => { - const cc = new AssociationGroupInfoCCInfoGet(host, { + const cc = new AssociationGroupInfoCCInfoGet({ nodeId: 1, groupId: 7, listMode: false, @@ -94,11 +92,11 @@ test("the InfoGet command should serialize correctly (refresh cache flag set)", 7, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the InfoGet command should serialize correctly (list mode flag set)", (t) => { - const cc = new AssociationGroupInfoCCInfoGet(host, { + const cc = new AssociationGroupInfoCCInfoGet({ nodeId: 1, groupId: 7, listMode: true, @@ -111,7 +109,7 @@ test("the InfoGet command should serialize correctly (list mode flag set)", (t) 0, // group id is ignored ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Info Report command should be deserialized correctly", (t) => { @@ -140,9 +138,10 @@ test("the Info Report command should be deserialized correctly", (t) => { 0, ]), ); - const cc = new AssociationGroupInfoCCInfoReport(host, { + const cc = new AssociationGroupInfoCCInfoReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.groups.length, 2); @@ -156,7 +155,7 @@ test("the Info Report command should be deserialized correctly", (t) => { }); test("the CommandListGet command should serialize correctly", (t) => { - const cc = new AssociationGroupInfoCCCommandListGet(host, { + const cc = new AssociationGroupInfoCCCommandListGet({ nodeId: 1, groupId: 6, allowCache: true, @@ -168,7 +167,7 @@ test("the CommandListGet command should serialize correctly", (t) => { 6, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CommandListReport command should be deserialized correctly", (t) => { @@ -185,9 +184,10 @@ test("the CommandListReport command should be deserialized correctly", (t) => { 0x05, ]), ); - const cc = new AssociationGroupInfoCCCommandListReport(host, { + const cc = new AssociationGroupInfoCCCommandListReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.groupId, 7); @@ -203,9 +203,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new AssociationGroupInfoCC(host, { + const cc: any = new AssociationGroupInfoCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, AssociationGroupInfoCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts b/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts index a047a46ff7f4..2780b0928c66 100644 --- a/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts @@ -11,7 +11,7 @@ import { CommandClasses } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; import test from "ava"; import * as nodeUtils from "../../node/utils"; -import { createTestNode } from "../mocks"; +import { type CreateTestNodeOptions, createTestNode } from "../mocks"; const host = createTestingHost(); @@ -25,17 +25,17 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const basicCC = new BasicCCGet(host, { nodeId: 1 }); + const basicCC = new BasicCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ BasicCommand.Get, // CC Command ]), ); - t.deepEqual(basicCC.serialize(), expected); + t.deepEqual(basicCC.serialize({} as any), expected); }); test("the Set command should serialize correctly", (t) => { - const basicCC = new BasicCCSet(host, { + const basicCC = new BasicCCSet({ nodeId: 2, targetValue: 55, }); @@ -45,7 +45,7 @@ test("the Set command should serialize correctly", (t) => { 55, // target value ]), ); - t.deepEqual(basicCC.serialize(), expected); + t.deepEqual(basicCC.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -55,9 +55,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 55, // current value ]), ); - const basicCC = new BasicCCReport(host, { + const basicCC = new BasicCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(basicCC.currentValue, 55); @@ -74,9 +75,10 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // duration ]), ); - const basicCC = new BasicCCReport(host, { + const basicCC = new BasicCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(basicCC.currentValue, 55); @@ -89,38 +91,33 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const basicCC: any = new BasicCC(host, { + const basicCC: any = new BasicCC({ nodeId: 2, data: serializedCC, + context: {} as any, }); t.is(basicCC.constructor, BasicCC); }); -test("getDefinedValueIDs() should include the target value for all endpoints except the node itself", (t) => { +test.only("getDefinedValueIDs() should include the target value for all endpoints except the node itself", (t) => { // Repro for GH#377 + const commandClasses: CreateTestNodeOptions["commandClasses"] = { + [CommandClasses.Basic]: { + version: 1, + }, + [CommandClasses["Multi Channel"]]: { + version: 2, + }, + }; const node2 = createTestNode(host, { id: 2, - numEndpoints: 2, - supportsCC(cc) { - switch (cc) { - case CommandClasses.Basic: - case CommandClasses["Multi Channel"]: - return true; - } - return false; - }, - getCCVersion(cc) { - switch (cc) { - case CommandClasses.Basic: - // We only support V1, so no report of the target value - return 1; - case CommandClasses["Multi Channel"]: - return 2; - } - return 0; + commandClasses, + endpoints: { + 1: { commandClasses }, + 2: { commandClasses }, }, }); - host.nodes.set(node2.id, node2); + host.setNode(node2.id, node2); const valueIDs = nodeUtils .getDefinedValueIDs(host as any, node2) @@ -134,7 +131,7 @@ test("getDefinedValueIDs() should include the target value for all endpoints exc }); test("BasicCCSet should expect no response", (t) => { - const cc = new BasicCCSet(host, { + const cc = new BasicCCSet({ nodeId: 2, endpoint: 2, targetValue: 7, @@ -143,12 +140,12 @@ test("BasicCCSet should expect no response", (t) => { }); test("BasicCCSet => BasicCCReport = unexpected", (t) => { - const ccRequest = new BasicCCSet(host, { + const ccRequest = new BasicCCSet({ nodeId: 2, endpoint: 2, targetValue: 7, }); - const ccResponse = new BasicCCReport(host, { + const ccResponse = new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }); @@ -157,17 +154,17 @@ test("BasicCCSet => BasicCCReport = unexpected", (t) => { }); test("BasicCCGet should expect a response", (t) => { - const cc = new BasicCCGet(host, { + const cc = new BasicCCGet({ nodeId: 2, }); t.true(cc.expectsCCResponse()); }); test("BasicCCGet => BasicCCReport = expected", (t) => { - const ccRequest = new BasicCCGet(host, { + const ccRequest = new BasicCCGet({ nodeId: 2, }); - const ccResponse = new BasicCCReport(host, { + const ccResponse = new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }); @@ -176,10 +173,10 @@ test("BasicCCGet => BasicCCReport = expected", (t) => { }); test("BasicCCGet => BasicCCReport (wrong node) = unexpected", (t) => { - const ccRequest = new BasicCCGet(host, { + const ccRequest = new BasicCCGet({ nodeId: 2, }); - const ccResponse = new BasicCCReport(host, { + const ccResponse = new BasicCCReport({ nodeId: (ccRequest.nodeId as number) + 1, currentValue: 7, }); @@ -188,10 +185,10 @@ test("BasicCCGet => BasicCCReport (wrong node) = unexpected", (t) => { }); test("BasicCCGet => BasicCCSet = unexpected", (t) => { - const ccRequest = new BasicCCGet(host, { + const ccRequest = new BasicCCGet({ nodeId: 2, }); - const ccResponse = new BasicCCSet(host, { + const ccResponse = new BasicCCSet({ nodeId: ccRequest.nodeId, targetValue: 7, }); @@ -200,7 +197,7 @@ test("BasicCCGet => BasicCCSet = unexpected", (t) => { }); test("Looking up CC values for a CC instance should work", (t) => { - const cc = new BasicCCGet(host, { + const cc = new BasicCCGet({ nodeId: 2, }); const values = getCCValues(cc) as typeof BasicCCValues; diff --git a/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts b/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts index 66cb5c62f775..34694b45787f 100644 --- a/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts @@ -7,18 +7,15 @@ import { BatteryReplacementStatus, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - test("the Get command should serialize correctly", (t) => { - const batteryCC = new BatteryCCGet(host, { nodeId: 1 }); + const batteryCC = new BatteryCCGet({ nodeId: 1 }); const expected = Buffer.from([ CommandClasses.Battery, // CC BatteryCommand.Get, // CC Command ]); - t.deepEqual(batteryCC.serialize(), expected); + t.deepEqual(batteryCC.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly: when the battery is not low", (t) => { @@ -27,9 +24,10 @@ test("the Report command (v1) should be deserialized correctly: when the battery BatteryCommand.Report, // CC Command 55, // current value ]); - const batteryCC = new BatteryCC(host, { + const batteryCC = new BatteryCC({ nodeId: 7, data: ccData, + context: {} as any, }) as BatteryCCReport; t.is(batteryCC.level, 55); @@ -42,9 +40,10 @@ test("the Report command (v1) should be deserialized correctly: when the battery BatteryCommand.Report, // CC Command 0xff, // current value ]); - const batteryCC = new BatteryCC(host, { + const batteryCC = new BatteryCC({ nodeId: 7, data: ccData, + context: {} as any, }) as BatteryCCReport; t.is(batteryCC.level, 0); @@ -59,9 +58,10 @@ test("the Report command (v2) should be deserialized correctly: all flags set", 0b00_1111_00, 1, // disconnected ]); - const batteryCC = new BatteryCC(host, { + const batteryCC = new BatteryCC({ nodeId: 7, data: ccData, + context: {} as any, }) as BatteryCCReport; t.true(batteryCC.rechargeable); @@ -79,9 +79,10 @@ test("the Report command (v2) should be deserialized correctly: charging status" 0b10_000000, // Maintaining 0, ]); - const batteryCC = new BatteryCC(host, { + const batteryCC = new BatteryCC({ nodeId: 7, data: ccData, + context: {} as any, }) as BatteryCCReport; t.is(batteryCC.chargingStatus, BatteryChargingStatus.Maintaining); @@ -95,9 +96,10 @@ test("the Report command (v2) should be deserialized correctly: recharge or repl 0b11, // Maintaining 0, ]); - const batteryCC = new BatteryCC(host, { + const batteryCC = new BatteryCC({ nodeId: 7, data: ccData, + context: {} as any, }) as BatteryCCReport; t.is(batteryCC.rechargeOrReplace, BatteryReplacementStatus.Now); @@ -108,9 +110,10 @@ test("deserializing an unsupported command should return an unspecified version CommandClasses.Battery, // CC 255, // not a valid command ]); - const basicCC: any = new BatteryCC(host, { + const basicCC: any = new BatteryCC({ nodeId: 7, data: serializedCC, + context: {} as any, }); t.is(basicCC.constructor, BatteryCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts b/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts index a2ab44a14af6..523931edfa9e 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts @@ -8,11 +8,8 @@ import { BinarySensorType, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -23,25 +20,25 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly (no sensor type)", (t) => { - const cc = new BinarySensorCCGet(host, { nodeId: 1 }); + const cc = new BinarySensorCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ BinarySensorCommand.Get, // CC Command BinarySensorType.Any, // sensor type ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Get command should serialize correctly", (t) => { - const cc = new BinarySensorCCGet(host, { + const cc = new BinarySensorCCGet({ nodeId: 1, sensorType: BinarySensorType.CO, }); const expected = buildCCBuffer( Buffer.from([BinarySensorCommand.Get, BinarySensorType.CO]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -51,9 +48,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0xff, // current value ]), ); - const cc = new BinarySensorCCReport(host, { + const cc = new BinarySensorCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.value, true); @@ -67,9 +65,10 @@ test("the Report command (v2) should be deserialized correctly", (t) => { BinarySensorType.CO2, ]), ); - const cc = new BinarySensorCCReport(host, { + const cc = new BinarySensorCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.value, false); @@ -77,13 +76,13 @@ test("the Report command (v2) should be deserialized correctly", (t) => { }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new BinarySensorCCSupportedGet(host, { nodeId: 1 }); + const cc = new BinarySensorCCSupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ BinarySensorCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedReport command should be deserialized correctly", (t) => { @@ -94,9 +93,10 @@ test("the SupportedReport command should be deserialized correctly", (t) => { 0b10, ]), ); - const cc = new BinarySensorCCSupportedReport(host, { + const cc = new BinarySensorCCSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedSensorTypes, [ @@ -112,9 +112,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new BinarySensorCC(host, { + const cc: any = new BinarySensorCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, BinarySensorCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts index f192a0a26a47..5e5450d8f262 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts @@ -6,11 +6,9 @@ import { BinarySwitchCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; +import { type GetSupportedCCVersion } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -21,21 +19,20 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new BinarySwitchCCGet(host, { nodeId: 1 }); + const cc = new BinarySwitchCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ BinarySwitchCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (no duration)", (t) => { - const cc = new BinarySwitchCCSet(host, { + const cc = new BinarySwitchCCSet({ nodeId: 2, targetValue: false, }); - cc.version = 1; const expected = buildCCBuffer( Buffer.from([ BinarySwitchCommand.Set, // CC Command @@ -43,17 +40,22 @@ test("the Set command should serialize correctly (no duration)", (t) => { 0xff, // default duration ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 1; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Set command should serialize correctly", (t) => { const duration = new Duration(2, "minutes"); - const cc = new BinarySwitchCCSet(host, { + const cc = new BinarySwitchCCSet({ nodeId: 2, targetValue: true, duration, }); - cc.version = 2; const expected = buildCCBuffer( Buffer.from([ BinarySwitchCommand.Set, // CC Command @@ -61,7 +63,13 @@ test("the Set command should serialize correctly", (t) => { duration.serializeSet(), ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 2; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -71,9 +79,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0xff, // current value ]), ); - const cc = new BinarySwitchCCReport(host, { + const cc = new BinarySwitchCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.currentValue, true); @@ -90,9 +99,10 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // duration ]), ); - const cc = new BinarySwitchCCReport(host, { + const cc = new BinarySwitchCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.currentValue, true); @@ -105,9 +115,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new BinarySwitchCC(host, { + const cc: any = new BinarySwitchCC({ nodeId: 2, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, BinarySwitchCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts b/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts index 72fa106040cd..64cefd9110ef 100644 --- a/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts @@ -7,42 +7,40 @@ import { InvalidCC, isEncapsulatingCommandClass, } from "@zwave-js/cc"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - test("should be detected as an encapsulating CC", (t) => { - const basicCCSet = new BasicCCSet(host, { + const basicCCSet = new BasicCCSet({ nodeId: 3, targetValue: 89, }); - const crc16 = CRC16CC.encapsulate(host, basicCCSet); + const crc16 = CRC16CC.encapsulate(basicCCSet); t.true(isEncapsulatingCommandClass(crc16)); }); test("should match the specs", (t) => { // SDS13783 contains the following sample encapsulated command: - const basicCCGet = new BasicCCGet(host, { nodeId: 1 }); - const crc16 = CRC16CC.encapsulate(host, basicCCGet); - const serialized = crc16.serialize(); + const basicCCGet = new BasicCCGet({ nodeId: 1 }); + const crc16 = CRC16CC.encapsulate(basicCCGet); + const serialized = crc16.serialize({} as any); const expected = Buffer.from("560120024d26", "hex"); t.deepEqual(serialized, expected); }); test("serialization and deserialization should be compatible", (t) => { - const basicCCSet = new BasicCCSet(host, { + const basicCCSet = new BasicCCSet({ nodeId: 3, targetValue: 89, }); - const crc16 = CRC16CC.encapsulate(host, basicCCSet); + const crc16 = CRC16CC.encapsulate(basicCCSet); t.is(crc16.nodeId, basicCCSet.nodeId); t.is(crc16.encapsulated, basicCCSet); - const serialized = crc16.serialize(); + const serialized = crc16.serialize({} as any); - const deserialized = CommandClass.from(host, { + const deserialized = CommandClass.from({ nodeId: basicCCSet.nodeId as number, data: serialized, + context: {} as any, }); t.is(deserialized.nodeId, basicCCSet.nodeId); const deserializedPayload = (deserialized as CRC16CCCommandEncapsulation) @@ -53,19 +51,20 @@ test("serialization and deserialization should be compatible", (t) => { }); test("deserializing a CC with a wrong checksum should result in an invalid CC", (t) => { - const basicCCSet = new BasicCCSet(host, { + const basicCCSet = new BasicCCSet({ nodeId: 3, targetValue: 89, }); - const crc16 = CRC16CC.encapsulate(host, basicCCSet); + const crc16 = CRC16CC.encapsulate(basicCCSet); t.is(crc16.nodeId, basicCCSet.nodeId); t.is(crc16.encapsulated, basicCCSet); - const serialized = crc16.serialize(); + const serialized = crc16.serialize({} as any); serialized[serialized.length - 1] ^= 0xff; - const decoded = CommandClass.from(host, { + const decoded = CommandClass.from({ nodeId: basicCCSet.nodeId as number, data: serialized, + context: {} as any, }); t.true(decoded instanceof InvalidCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts b/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts index 233d338e5e1f..0aa07df4f406 100644 --- a/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts @@ -10,11 +10,8 @@ import { CentralSceneKeys, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -25,7 +22,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the ConfigurationGet command should serialize correctly", (t) => { - const cc = new CentralSceneCCConfigurationGet(host, { + const cc = new CentralSceneCCConfigurationGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -33,11 +30,11 @@ test("the ConfigurationGet command should serialize correctly", (t) => { CentralSceneCommand.ConfigurationGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationSet command should serialize correctly (flags set)", (t) => { - const cc = new CentralSceneCCConfigurationSet(host, { + const cc = new CentralSceneCCConfigurationSet({ nodeId: 2, slowRefresh: true, }); @@ -47,11 +44,11 @@ test("the ConfigurationSet command should serialize correctly (flags set)", (t) 0b1000_0000, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationSet command should serialize correctly (flags not set)", (t) => { - const cc = new CentralSceneCCConfigurationSet(host, { + const cc = new CentralSceneCCConfigurationSet({ nodeId: 2, slowRefresh: false, }); @@ -61,7 +58,7 @@ test("the ConfigurationSet command should serialize correctly (flags not set)", 0, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationReport command should be deserialized correctly", (t) => { @@ -71,16 +68,17 @@ test("the ConfigurationReport command should be deserialized correctly", (t) => 0b1000_0000, ]), ); - const cc = new CentralSceneCCConfigurationReport(host, { + const cc = new CentralSceneCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.slowRefresh, true); }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new CentralSceneCCSupportedGet(host, { + const cc = new CentralSceneCCSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -88,7 +86,7 @@ test("the SupportedGet command should serialize correctly", (t) => { CentralSceneCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedReport command should be deserialized correctly", (t) => { @@ -103,9 +101,10 @@ test("the SupportedReport command should be deserialized correctly", (t) => { 0, ]), ); - const cc = new CentralSceneCCSupportedReport(host, { + const cc = new CentralSceneCCSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.sceneCount, 2); @@ -125,9 +124,10 @@ test("the Notification command should be deserialized correctly", (t) => { 8, // scene number ]), ); - const cc = new CentralSceneCCNotification(host, { + const cc = new CentralSceneCCNotification({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.sequenceNumber, 7); @@ -146,9 +146,10 @@ test("the Notification command should be deserialized correctly (KeyHeldDown)", 8, // scene number ]), ); - const cc = new CentralSceneCCNotification(host, { + const cc = new CentralSceneCCNotification({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.sequenceNumber, 7); @@ -161,9 +162,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new CentralSceneCC(host, { + const cc: any = new CentralSceneCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, CentralSceneCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts index e361794a6589..957a23f0fd1e 100644 --- a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts @@ -16,11 +16,9 @@ import { ZWaveErrorCodes, assertZWaveError, } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; +import { type GetSupportedCCVersion } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -31,7 +29,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the SupportedGet command should serialize correctly", (t) => { - const cc = new ColorSwitchCCSupportedGet(host, { + const cc = new ColorSwitchCCSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -39,7 +37,7 @@ test("the SupportedGet command should serialize correctly", (t) => { ColorSwitchCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedReport command should deserialize correctly", (t) => { @@ -50,9 +48,10 @@ test("the SupportedReport command should deserialize correctly", (t) => { 0b0000_0001, ]), ); - const cc = new ColorSwitchCCSupportedReport(host, { + const cc = new ColorSwitchCCSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedColorComponents, [ @@ -71,7 +70,7 @@ test("the SupportedReport command should deserialize correctly", (t) => { }); test("the Get command should serialize correctly", (t) => { - const cc = new ColorSwitchCCGet(host, { + const cc = new ColorSwitchCCGet({ nodeId: 1, colorComponent: ColorComponent.Red, }); @@ -81,7 +80,7 @@ test("the Get command should serialize correctly", (t) => { 2, // Color Component ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should deserialize correctly (version 1)", (t) => { @@ -92,9 +91,10 @@ test("the Report command should deserialize correctly (version 1)", (t) => { 0b1111_1111, // value: 255 ]), ); - const cc = new ColorSwitchCCReport(host, { + const cc = new ColorSwitchCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.colorComponent, ColorComponent.Red); @@ -113,9 +113,10 @@ test("the Report command should deserialize correctly (version 3)", (t) => { 0b0000_0001, // duration: 1 ]), ); - const cc = new ColorSwitchCCReport(host, { + const cc = new ColorSwitchCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.colorComponent, ColorComponent.Red); @@ -126,7 +127,7 @@ test("the Report command should deserialize correctly (version 3)", (t) => { }); test("the Set command should serialize correctly (without duration)", (t) => { - const cc = new ColorSwitchCCSet(host, { + const cc = new ColorSwitchCCSet({ nodeId: 1, red: 128, green: 255, @@ -144,11 +145,18 @@ test("the Set command should serialize correctly (without duration)", (t) => { 0xff, // duration: default ]), ); - t.deepEqual(cc.serialize(), expected); + + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 0; // Default to implemented version + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Set command should serialize correctly (version 2)", (t) => { - const cc = new ColorSwitchCCSet(host, { + const cc = new ColorSwitchCCSet({ nodeId: 1, red: 128, green: 255, @@ -167,11 +175,17 @@ test("the Set command should serialize correctly (version 2)", (t) => { 0b0000_0001, // duration: 1 ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 2; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the StartLevelChange command should serialize correctly", (t) => { - const cc = new ColorSwitchCCStartLevelChange(host, { + const cc = new ColorSwitchCCStartLevelChange({ nodeId: 1, startLevel: 5, ignoreStartLevel: true, @@ -179,8 +193,6 @@ test("the StartLevelChange command should serialize correctly", (t) => { colorComponent: ColorComponent.Red, duration: new Duration(1, "seconds"), }); - cc.version = 3; - const expected = buildCCBuffer( Buffer.from([ ColorSwitchCommand.StartLevelChange, @@ -190,11 +202,17 @@ test("the StartLevelChange command should serialize correctly", (t) => { 0b0000_0001, // duration: 1 ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 3; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the StopLevelChange command should serialize correctly", (t) => { - const cc = new ColorSwitchCCStopLevelChange(host, { + const cc = new ColorSwitchCCStopLevelChange({ nodeId: 1, colorComponent: ColorComponent.Red, }); @@ -205,7 +223,7 @@ test("the StopLevelChange command should serialize correctly", (t) => { 0b0000_0010, // color: red ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the setValue API verifies that targetColor isn't set with non-numeric keys", async (t) => { diff --git a/packages/zwave-js/src/lib/test/cc/CommandClass.nonImplemented.test.ts b/packages/zwave-js/src/lib/test/cc/CommandClass.nonImplemented.test.ts index 6eabd644a761..40ba6a78e7fc 100644 --- a/packages/zwave-js/src/lib/test/cc/CommandClass.nonImplemented.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CommandClass.nonImplemented.test.ts @@ -9,7 +9,7 @@ integrationTest( async testBody(t, driver, node, mockController, mockNode) { // This CC will never be supported (certification requirement) - const cc = new CommandClass(driver, { + const cc = new CommandClass({ nodeId: 2, ccId: CommandClasses["Anti-Theft"], ccCommand: 0x02, diff --git a/packages/zwave-js/src/lib/test/cc/CommandClass.persistValues.test.ts b/packages/zwave-js/src/lib/test/cc/CommandClass.persistValues.test.ts index e5309f307398..83a03979d623 100644 --- a/packages/zwave-js/src/lib/test/cc/CommandClass.persistValues.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CommandClass.persistValues.test.ts @@ -71,7 +71,7 @@ test(`persistValues() should not update "interviewComplete" in the value DB`, (t const { node2, driver } = t.context; // Repro for #383 - const cc = new BasicCCSet(driver, { + const cc = new BasicCCSet({ nodeId: node2.id, targetValue: 55, }); @@ -91,7 +91,7 @@ test(`persistValues() should not update "interviewComplete" in the value DB`, (t test(`persistValues() should not store values marked as "events" (non-stateful)`, async (t) => { const { node2, driver } = t.context; - const cc = new CentralSceneCCNotification(driver, { + const cc = new CentralSceneCCNotification({ nodeId: node2.id, data: Buffer.from([ CommandClasses["Central Scene"], @@ -100,6 +100,7 @@ test(`persistValues() should not store values marked as "events" (non-stateful)` CentralSceneKeys.KeyPressed, 1, // scene number ]), + context: {} as any, }); // Central Scene should use the value notification event instead of added/updated diff --git a/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts b/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts index 1c1686546186..034bf949489f 100644 --- a/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts @@ -10,12 +10,9 @@ import { implementedVersion, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; import { SendDataRequest } from "../../serialapi/transport/SendDataMessages"; -const host = createTestingHost(); - @implementedVersion(7) @commandClass(0xffff as any) class DummyCC extends CommandClass {} @@ -29,27 +26,28 @@ class DummyCCSubClass2 extends DummyCC { test(`creating and serializing should work for unspecified commands`, (t) => { // Repro for #1219 - const cc = new CommandClass(host, { + const cc = new CommandClass({ nodeId: 2, ccId: 0x5d, ccCommand: 0x02, payload: Buffer.from([1, 2, 3]), }); - const msg = new SendDataRequest(host, { + const msg = new SendDataRequest({ command: cc, callbackId: 0xfe, }); t.deepEqual( - msg.serialize(), + msg.serialize({} as any), Buffer.from("010c001302055d0201020325fe63", "hex"), ); }); test("from() returns an un-specialized instance when receiving a non-implemented CC", (t) => { // This is a Node Provisioning CC. Change it when that CC is implemented - const cc = CommandClass.from(host, { + const cc = CommandClass.from({ data: Buffer.from("78030100", "hex"), nodeId: 5, + context: {} as any, }); t.is(cc.constructor, CommandClass); t.is(cc.nodeId, 5); @@ -60,16 +58,17 @@ test("from() returns an un-specialized instance when receiving a non-implemented test("from() does not throw when the CC is implemented", (t) => { t.notThrows(() => - CommandClass.from(host, { + CommandClass.from({ // CRC-16 with BasicCC data: Buffer.from("560120024d26", "hex"), nodeId: 5, + context: {} as any, }) ); }); test("getImplementedVersion() should return the implemented version for a CommandClass instance", (t) => { - const cc = new BasicCC(host, { nodeId: 1 }); + const cc = new BasicCC({ nodeId: 1 }); t.is(getImplementedVersion(cc), 2); }); @@ -80,11 +79,11 @@ test("getImplementedVersion() should return the implemented version for a numeri test("getImplementedVersion() should return 0 for a non-existing CC", (t) => { const cc = -1; - t.is(getImplementedVersion(cc), 0); + t.is(getImplementedVersion(cc as any), 0); }); test("getImplementedVersion() should work with inheritance", (t) => { - const cc = new BasicCCGet(host, { nodeId: 1 }); + const cc = new BasicCCGet({ nodeId: 1 }); t.is(getImplementedVersion(cc), 2); }); @@ -97,12 +96,12 @@ test("getImplementedVersionStatic() should work on inherited classes", (t) => { }); test("expectMoreMessages() returns false by default", (t) => { - const cc = new DummyCC(host, { nodeId: 1 }); + const cc = new DummyCC({ nodeId: 1 }); t.false(cc.expectMoreMessages([])); }); test("getExpectedCCResponse() returns the expected CC response like it was defined", (t) => { - const cc = new DummyCCSubClass2(host, { nodeId: 1 }); + const cc = new DummyCCSubClass2({ nodeId: 1 }); const actual = getExpectedCCResponse(cc); t.is(actual, DummyCCSubClass1); }); diff --git a/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts b/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts index eb6244b39568..ac5bf54758d2 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts @@ -40,17 +40,17 @@ valueDB2.setValue(DoorLockCCValues.boltSupported.id, true); valueDB2.setValue(DoorLockCCValues.latchSupported.id, true); test("the OperationGet command should serialize correctly", (t) => { - const cc = new DoorLockCCOperationGet(host, { nodeId: 1 }); + const cc = new DoorLockCCOperationGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ DoorLockCommand.OperationGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the OperationSet command should serialize correctly", (t) => { - const cc = new DoorLockCCOperationSet(host, { + const cc = new DoorLockCCOperationSet({ nodeId: 2, mode: DoorLockMode.OutsideUnsecured, }); @@ -60,7 +60,7 @@ test("the OperationSet command should serialize correctly", (t) => { 0x20, // target value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the OperationReport command (v1-v3) should be deserialized correctly", (t) => { @@ -74,9 +74,10 @@ test("the OperationReport command (v1-v3) should be deserialized correctly", (t) 20, // timeout seconds ]), ); - const cc = new DoorLockCCOperationReport(host, { + const cc = new DoorLockCCOperationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.currentMode, DoorLockMode.InsideUnsecuredWithTimeout); @@ -103,9 +104,10 @@ test("the OperationReport command (v4) should be deserialized correctly", (t) => 0x01, // 1 second left ]), ); - const cc = new DoorLockCCOperationReport(host, { + const cc = new DoorLockCCOperationReport({ nodeId: 2, data: ccData, + context: {} as any, }); cc.persistValues(host); @@ -128,13 +130,13 @@ test("the OperationReport command (v4) should be deserialized correctly", (t) => }); test("the ConfigurationGet command should serialize correctly", (t) => { - const cc = new DoorLockCCConfigurationGet(host, { nodeId: 1 }); + const cc = new DoorLockCCConfigurationGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ DoorLockCommand.ConfigurationGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationReport command (v1-v3) should be deserialized correctly", (t) => { @@ -147,9 +149,10 @@ test("the ConfigurationReport command (v1-v3) should be deserialized correctly", 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { + const cc = new DoorLockCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.operationType, DoorLockOperationType.Timed); @@ -182,9 +185,10 @@ test("the ConfigurationReport command must ignore invalid timeouts (constant)", 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { + const cc = new DoorLockCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.lockTimeoutConfiguration, undefined); @@ -200,9 +204,10 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid minu 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { + const cc = new DoorLockCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.lockTimeoutConfiguration, undefined); @@ -218,9 +223,10 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid seco 0xff, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { + const cc = new DoorLockCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.lockTimeoutConfiguration, undefined); @@ -242,9 +248,10 @@ test("the ConfigurationReport command (v4) should be deserialized correctly", (t 0b01, // flags ]), ); - const cc = new DoorLockCCConfigurationReport(host, { + const cc = new DoorLockCCConfigurationReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.autoRelockTime, 0xff01); @@ -254,7 +261,7 @@ test("the ConfigurationReport command (v4) should be deserialized correctly", (t }); test("the ConfigurationSet command (v4) should serialize correctly", (t) => { - const cc = new DoorLockCCConfigurationSet(host, { + const cc = new DoorLockCCConfigurationSet({ nodeId: 2, operationType: DoorLockOperationType.Timed, outsideHandlesCanOpenDoorConfiguration: [false, true, true, true], @@ -279,17 +286,17 @@ test("the ConfigurationSet command (v4) should serialize correctly", (t) => { 0b1, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CapabilitiesGet command should serialize correctly", (t) => { - const cc = new DoorLockCCCapabilitiesGet(host, { nodeId: 1 }); + const cc = new DoorLockCCCapabilitiesGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ DoorLockCommand.CapabilitiesGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CapabilitiesReport command should be deserialized correctly", (t) => { @@ -307,9 +314,10 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { 0b1010, // feature flags ]), ); - const cc = new DoorLockCCCapabilitiesReport(host, { + const cc = new DoorLockCCCapabilitiesReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedOperationTypes, [ @@ -341,7 +349,7 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { // 1, // duration // ]), // ); -// const cc = new DoorLockCCReport(host, { data: ccData }); +// const cc = new DoorLockCCReport({ data: ccData }); // t.is(cc.currentValue, 55); // t.is(cc.targetValue, 66); @@ -354,7 +362,7 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { // 1, // Buffer.from([255]), // not a valid command // ); -// const cc: any = new DoorLockCC(host, { +// const cc: any = new DoorLockCC({ // data: serializedCC, // }); // t.is(cc.constructor, DoorLockCC); diff --git a/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts b/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts index 344048ea52f5..c8e234abb8c4 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts @@ -7,11 +7,8 @@ import { DoorLockLoggingEventType, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -22,7 +19,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the RecordsCountGet command should serialize correctly", (t) => { - const cc = new DoorLockLoggingCCRecordsSupportedGet(host, { + const cc = new DoorLockLoggingCCRecordsSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -30,7 +27,7 @@ test("the RecordsCountGet command should serialize correctly", (t) => { DoorLockLoggingCommand.RecordsSupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the RecordsCountReport command should be deserialized correctly", (t) => { @@ -40,16 +37,17 @@ test("the RecordsCountReport command should be deserialized correctly", (t) => { 0x14, // max records supported (20) ]), ); - const cc = new DoorLockLoggingCCRecordsSupportedReport(host, { + const cc = new DoorLockLoggingCCRecordsSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.recordsCount, 20); }); test("the RecordGet command should serialize correctly", (t) => { - const cc = new DoorLockLoggingCCRecordGet(host, { + const cc = new DoorLockLoggingCCRecordGet({ nodeId: 1, recordNumber: 1, }); @@ -59,7 +57,7 @@ test("the RecordGet command should serialize correctly", (t) => { 1, // Record Number ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the RecordReport command should be deserialized correctly", (t) => { @@ -81,9 +79,10 @@ test("the RecordReport command should be deserialized correctly", (t) => { ]), ); - const cc = new DoorLockLoggingCCRecordReport(host, { + const cc = new DoorLockLoggingCCRecordReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.recordNumber, 7); diff --git a/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts b/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts index 4b52b4d1e6b8..168677275714 100644 --- a/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts @@ -12,11 +12,8 @@ import { EntryControlEventTypes, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -45,9 +42,10 @@ test("the Notification command should deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCNotification(host, { + const cc = new EntryControlCCNotification({ nodeId: 1, data, + context: {} as any, }); t.deepEqual(cc.sequenceNumber, 1); @@ -57,7 +55,7 @@ test("the Notification command should deserialize correctly", (t) => { }); test("the ConfigurationGet command should serialize correctly", (t) => { - const cc = new EntryControlCCConfigurationGet(host, { + const cc = new EntryControlCCConfigurationGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -65,11 +63,11 @@ test("the ConfigurationGet command should serialize correctly", (t) => { EntryControlCommand.ConfigurationGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationSet command should serialize correctly", (t) => { - const cc = new EntryControlCCConfigurationSet(host, { + const cc = new EntryControlCCConfigurationSet({ nodeId: 1, keyCacheSize: 1, keyCacheTimeout: 2, @@ -81,7 +79,7 @@ test("the ConfigurationSet command should serialize correctly", (t) => { 0x2, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ConfigurationReport command should be deserialize correctly", (t) => { @@ -93,9 +91,10 @@ test("the ConfigurationReport command should be deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCConfigurationReport(host, { + const cc = new EntryControlCCConfigurationReport({ nodeId: 1, data, + context: {} as any, }); t.deepEqual(cc.keyCacheSize, 1); @@ -103,7 +102,7 @@ test("the ConfigurationReport command should be deserialize correctly", (t) => { }); test("the EventSupportedGet command should serialize correctly", (t) => { - const cc = new EntryControlCCEventSupportedGet(host, { + const cc = new EntryControlCCEventSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -111,7 +110,7 @@ test("the EventSupportedGet command should serialize correctly", (t) => { EntryControlCommand.EventSupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the EventSupportedReport command should be deserialize correctly", (t) => { @@ -132,9 +131,10 @@ test("the EventSupportedReport command should be deserialize correctly", (t) => ]), ); - const cc = new EntryControlCCEventSupportedReport(host, { + const cc = new EntryControlCCEventSupportedReport({ nodeId: 1, data, + context: {} as any, }); t.deepEqual(cc.supportedDataTypes, [EntryControlDataTypes.ASCII]); @@ -151,13 +151,13 @@ test("the EventSupportedReport command should be deserialize correctly", (t) => }); test("the KeySupportedGet command should serialize correctly", (t) => { - const cc = new EntryControlCCKeySupportedGet(host, { nodeId: 1 }); + const cc = new EntryControlCCKeySupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ EntryControlCommand.KeySupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the KeySupportedReport command should be deserialize correctly", (t) => { @@ -169,9 +169,10 @@ test("the KeySupportedReport command should be deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCKeySupportedReport(host, { + const cc = new EntryControlCCKeySupportedReport({ nodeId: 1, data, + context: {} as any, }); t.deepEqual(cc.supportedKeys, [1, 3, 4, 6]); diff --git a/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts b/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts index 0da36b676795..7d21f7330e97 100644 --- a/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/FibaroCC.test.ts @@ -6,11 +6,8 @@ import { FibaroVenetianBlindCCSet, } from "@zwave-js/cc/manufacturerProprietary/FibaroCC"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -26,7 +23,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Set Tilt command should serialize correctly", (t) => { - const cc = new FibaroVenetianBlindCCSet(host, { + const cc = new FibaroVenetianBlindCCSet({ nodeId: 2, tilt: 99, }); @@ -38,7 +35,7 @@ test("the Set Tilt command should serialize correctly", (t) => { 0x63, // Tilt ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly", (t) => { @@ -50,9 +47,10 @@ test("the Report command should be deserialized correctly", (t) => { 0x00, // Tilt ]), ); - const cc = CommandClass.from(host, { + const cc = CommandClass.from({ nodeId: 2, data: ccData, + context: {} as any, }); t.true(cc instanceof FibaroVenetianBlindCCReport); t.is((cc as FibaroVenetianBlindCCReport).position, 0); @@ -60,7 +58,7 @@ test("the Report command should be deserialized correctly", (t) => { }); test("FibaroVenetianBlindCCSet should expect no response", (t) => { - const cc = new FibaroVenetianBlindCCSet(host, { + const cc = new FibaroVenetianBlindCCSet({ nodeId: 2, tilt: 7, }); @@ -68,18 +66,18 @@ test("FibaroVenetianBlindCCSet should expect no response", (t) => { }); test("FibaroVenetianBlindCCGet should expect a response", (t) => { - const cc = new FibaroVenetianBlindCCGet(host, { + const cc = new FibaroVenetianBlindCCGet({ nodeId: 2, }); t.true(cc.expectsCCResponse()); }); test("FibaroVenetianBlindCCSet => FibaroVenetianBlindCCReport = unexpected", (t) => { - const ccRequest = new FibaroVenetianBlindCCSet(host, { + const ccRequest = new FibaroVenetianBlindCCSet({ nodeId: 2, tilt: 7, }); - const ccResponse = new FibaroVenetianBlindCCReport(host, { + const ccResponse = new FibaroVenetianBlindCCReport({ nodeId: 2, data: buildCCBuffer( Buffer.from([ @@ -89,16 +87,17 @@ test("FibaroVenetianBlindCCSet => FibaroVenetianBlindCCReport = unexpected", (t) 0x07, // Tilt ]), ), + context: {} as any, }); t.false(ccRequest.isExpectedCCResponse(ccResponse)); }); test("FibaroVenetianBlindCCGet => FibaroVenetianBlindCCReport = expected", (t) => { - const ccRequest = new FibaroVenetianBlindCCGet(host, { + const ccRequest = new FibaroVenetianBlindCCGet({ nodeId: 2, }); - const ccResponse = new FibaroVenetianBlindCCReport(host, { + const ccResponse = new FibaroVenetianBlindCCReport({ nodeId: 2, data: buildCCBuffer( Buffer.from([ @@ -108,6 +107,7 @@ test("FibaroVenetianBlindCCGet => FibaroVenetianBlindCCReport = expected", (t) = 0x07, // Tilt ]), ), + context: {} as any, }); t.true(ccRequest.isExpectedCCResponse(ccResponse)); diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts index d5f42b35de02..fb39ca7a96c7 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts @@ -25,7 +25,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new HumidityControlModeCCGet(host, { + const cc = new HumidityControlModeCCGet({ nodeId, }); const expected = buildCCBuffer( @@ -33,11 +33,11 @@ test("the Get command should serialize correctly", (t) => { HumidityControlModeCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly", (t) => { - const cc = new HumidityControlModeCCSet(host, { + const cc = new HumidityControlModeCCSet({ nodeId, mode: HumidityControlMode.Auto, }); @@ -47,7 +47,7 @@ test("the Set command should serialize correctly", (t) => { 0x03, // target value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly", (t) => { @@ -57,9 +57,10 @@ test("the Report command should be deserialized correctly", (t) => { HumidityControlMode.Auto, // current value ]), ); - const cc = new HumidityControlModeCCReport(host, { + const cc = new HumidityControlModeCCReport({ nodeId, data: ccData, + context: {} as any, }); t.is(cc.mode, HumidityControlMode.Auto); @@ -72,9 +73,10 @@ test("the Report command should set the correct value", (t) => { HumidityControlMode.Auto, // current value ]), ); - const report = new HumidityControlModeCCReport(host, { + const report = new HumidityControlModeCCReport({ nodeId, data: ccData, + context: {} as any, }); report.persistValues(host); @@ -92,9 +94,10 @@ test("the Report command should set the correct metadata", (t) => { HumidityControlMode.Auto, // current value ]), ); - const cc = new HumidityControlModeCCReport(host, { + const cc = new HumidityControlModeCCReport({ nodeId, data: ccData, + context: {} as any, }); cc.persistValues(host); @@ -109,7 +112,7 @@ test("the Report command should set the correct metadata", (t) => { }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new HumidityControlModeCCSupportedGet(host, { + const cc = new HumidityControlModeCCSupportedGet({ nodeId, }); const expected = buildCCBuffer( @@ -117,7 +120,7 @@ test("the SupportedGet command should serialize correctly", (t) => { HumidityControlModeCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedReport command should be deserialized correctly", (t) => { @@ -127,9 +130,10 @@ test("the SupportedReport command should be deserialized correctly", (t) => { (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), ); - const cc = new HumidityControlModeCCSupportedReport(host, { + const cc = new HumidityControlModeCCSupportedReport({ nodeId, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedModes, [ @@ -145,9 +149,10 @@ test("the SupportedReport command should set the correct metadata", (t) => { (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), ); - const cc = new HumidityControlModeCCSupportedReport(host, { + const cc = new HumidityControlModeCCSupportedReport({ nodeId, data: ccData, + context: {} as any, }); cc.persistValues(host); diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts index 68618f0b605e..158b1926bf2f 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts @@ -5,11 +5,8 @@ import { HumidityControlOperatingStateCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -20,7 +17,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new HumidityControlOperatingStateCCGet(host, { + const cc = new HumidityControlOperatingStateCCGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -28,7 +25,7 @@ test("the Get command should serialize correctly", (t) => { HumidityControlOperatingStateCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly", (t) => { @@ -38,9 +35,10 @@ test("the Report command should be deserialized correctly", (t) => { HumidityControlOperatingState.Humidifying, // state ]), ); - const cc = new HumidityControlOperatingStateCCReport(host, { + const cc = new HumidityControlOperatingStateCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.state, HumidityControlOperatingState.Humidifying); diff --git a/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts b/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts index cf3d3a95f401..4166dbda4fdc 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts @@ -28,7 +28,7 @@ const host = createTestingHost(); const nodeId = 2; test("the Get command should serialize correctly", (t) => { - const cc = new HumidityControlSetpointCCGet(host, { + const cc = new HumidityControlSetpointCCGet({ nodeId: nodeId, setpointType: HumidityControlSetpointType.Humidifier, }); @@ -38,11 +38,11 @@ test("the Get command should serialize correctly", (t) => { HumidityControlSetpointType.Humidifier, // type ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly", (t) => { - const cc = new HumidityControlSetpointCCSet(host, { + const cc = new HumidityControlSetpointCCSet({ nodeId: nodeId, setpointType: HumidityControlSetpointType.Humidifier, value: 57, @@ -57,7 +57,7 @@ test("the Set command should serialize correctly", (t) => { encodeFloatWithScale(57, 1), ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly", (t) => { @@ -70,9 +70,10 @@ test("the Report command should be deserialized correctly", (t) => { encodeFloatWithScale(12, 1), ]), ); - const cc = new HumidityControlSetpointCCReport(host, { + const cc = new HumidityControlSetpointCCReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); t.deepEqual(cc.type, HumidityControlSetpointType.Humidifier); @@ -95,9 +96,10 @@ test("the Report command should set the correct value", (t) => { encodeFloatWithScale(12, 1), ]), ); - const report = new HumidityControlSetpointCCReport(host, { + const report = new HumidityControlSetpointCCReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); report.persistValues(host); @@ -126,9 +128,10 @@ test("the Report command should set the correct metadata", (t) => { encodeFloatWithScale(12, 1), ]), ); - const report = new HumidityControlSetpointCCReport(host, { + const report = new HumidityControlSetpointCCReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); report.persistValues(host); @@ -146,7 +149,7 @@ test("the Report command should set the correct metadata", (t) => { }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new HumidityControlSetpointCCSupportedGet(host, { + const cc = new HumidityControlSetpointCCSupportedGet({ nodeId: nodeId, }); const expected = buildCCBuffer( @@ -154,7 +157,7 @@ test("the SupportedGet command should serialize correctly", (t) => { HumidityControlSetpointCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedReport command should be deserialized correctly", (t) => { @@ -165,9 +168,10 @@ test("the SupportedReport command should be deserialized correctly", (t) => { | (1 << HumidityControlSetpointType.Auto), ]), ); - const cc = new HumidityControlSetpointCCSupportedReport(host, { + const cc = new HumidityControlSetpointCCSupportedReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedSetpointTypes, [ @@ -184,9 +188,10 @@ test("the SupportedReport command should set the correct value", (t) => { | (1 << HumidityControlSetpointType.Auto), ]), ); - const report = new HumidityControlSetpointCCSupportedReport(host, { + const report = new HumidityControlSetpointCCSupportedReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); report.persistValues(host); @@ -201,7 +206,7 @@ test("the SupportedReport command should set the correct value", (t) => { }); test("the ScaleSupportedGet command should serialize correctly", (t) => { - const cc = new HumidityControlSetpointCCScaleSupportedGet(host, { + const cc = new HumidityControlSetpointCCScaleSupportedGet({ nodeId: nodeId, setpointType: HumidityControlSetpointType.Auto, }); @@ -211,7 +216,7 @@ test("the ScaleSupportedGet command should serialize correctly", (t) => { HumidityControlSetpointType.Auto, // type ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the ScaleSupportedReport command should be deserialized correctly", (t) => { @@ -221,9 +226,10 @@ test("the ScaleSupportedReport command should be deserialized correctly", (t) => 0b11, // percent + absolute ]), ); - const cc = new HumidityControlSetpointCCScaleSupportedReport(host, { + const cc = new HumidityControlSetpointCCScaleSupportedReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); t.deepEqual(cc.supportedScales, [0, 1]); @@ -239,7 +245,7 @@ test("the ScaleSupportedReport command should be deserialized correctly", (t) => }); test("the CapabilitiesGet command should serialize correctly", (t) => { - const cc = new HumidityControlSetpointCCCapabilitiesGet(host, { + const cc = new HumidityControlSetpointCCCapabilitiesGet({ nodeId: nodeId, setpointType: HumidityControlSetpointType.Auto, }); @@ -249,7 +255,7 @@ test("the CapabilitiesGet command should serialize correctly", (t) => { HumidityControlSetpointType.Auto, // type ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CapabilitiesReport command should be deserialized correctly", (t) => { @@ -263,9 +269,10 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { encodeFloatWithScale(90, 1), ]), ); - const cc = new HumidityControlSetpointCCCapabilitiesReport(host, { + const cc = new HumidityControlSetpointCCCapabilitiesReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); t.deepEqual(cc.type, HumidityControlSetpointType.Humidifier); @@ -286,9 +293,10 @@ test("the CapabilitiesReport command should set the correct metadata", (t) => { encodeFloatWithScale(90, 1), ]), ); - const report = new HumidityControlSetpointCCCapabilitiesReport(host, { + const report = new HumidityControlSetpointCCCapabilitiesReport({ nodeId: nodeId, data: ccData, + context: {} as any, }); report.persistValues(host); diff --git a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts index 626c640c93f2..05a6cd7f2f03 100644 --- a/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/IndicatorCC.test.ts @@ -24,17 +24,17 @@ function buildCCBuffer(payload: Buffer): Buffer { const host = createTestingHost(); test("the Get command (V1) should serialize correctly", (t) => { - const cc = new IndicatorCCGet(host, { nodeId: 1 }); + const cc = new IndicatorCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ IndicatorCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Get command (V2) should serialize correctly", (t) => { - const cc = new IndicatorCCGet(host, { + const cc = new IndicatorCCGet({ nodeId: 1, indicatorId: 5, }); @@ -44,11 +44,11 @@ test("the Get command (V2) should serialize correctly", (t) => { 5, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command (v1) should serialize correctly", (t) => { - const cc = new IndicatorCCSet(host, { + const cc = new IndicatorCCSet({ nodeId: 2, value: 23, }); @@ -58,11 +58,11 @@ test("the Set command (v1) should serialize correctly", (t) => { 23, // value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command (v2) should serialize correctly", (t) => { - const cc = new IndicatorCCSet(host, { + const cc = new IndicatorCCSet({ nodeId: 2, values: [ { @@ -90,7 +90,7 @@ test("the Set command (v2) should serialize correctly", (t) => { 1, // value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -100,9 +100,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 55, // value ]), ); - const cc = new IndicatorCCReport(host, { + const cc = new IndicatorCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.indicator0Value, 55); @@ -123,9 +124,10 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // value ]), ); - const cc = new IndicatorCCReport(host, { + const cc = new IndicatorCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); // Boolean indicators are only interpreted during persistValues cc.persistValues(host); @@ -149,9 +151,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new IndicatorCC(host, { + const cc: any = new IndicatorCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, IndicatorCC); }); @@ -160,7 +163,6 @@ test("the value IDs should be translated properly", (t) => { const valueId = IndicatorCCValues.valueV2(0x43, 2).endpoint(2); const testNode = createTestNode(host, { id: 2 }); const ccInstance = CommandClass.createInstanceUnchecked( - host, testNode, CommandClasses.Indicator, )!; diff --git a/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts b/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts index 200531664cfe..6fd678841444 100644 --- a/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts @@ -6,11 +6,8 @@ import { LanguageCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -21,17 +18,17 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new LanguageCCGet(host, { nodeId: 1 }); + const cc = new LanguageCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ LanguageCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (w/o country code)", (t) => { - const cc = new LanguageCCSet(host, { + const cc = new LanguageCCSet({ nodeId: 2, language: "deu", }); @@ -44,11 +41,11 @@ test("the Set command should serialize correctly (w/o country code)", (t) => { 0x75, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (w/ country code)", (t) => { - const cc = new LanguageCCSet(host, { + const cc = new LanguageCCSet({ nodeId: 2, language: "deu", country: "DE", @@ -65,7 +62,7 @@ test("the Set command should serialize correctly (w/ country code)", (t) => { 0x45, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly (w/o country code)", (t) => { @@ -78,9 +75,10 @@ test("the Report command should be deserialized correctly (w/o country code)", ( 0x75, ]), ); - const cc = new LanguageCCReport(host, { + const cc = new LanguageCCReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.is(cc.language, "deu"); @@ -100,9 +98,10 @@ test("the Report command should be deserialized correctly (w/ country code)", (t 0x45, ]), ); - const cc = new LanguageCCReport(host, { + const cc = new LanguageCCReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.is(cc.language, "deu"); @@ -113,9 +112,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new LanguageCC(host, { + const cc: any = new LanguageCC({ nodeId: 4, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, LanguageCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts b/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts index 2d4e4e041b45..66e1ba7dac7e 100644 --- a/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts @@ -4,11 +4,8 @@ import { ManufacturerSpecificCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -19,13 +16,13 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new ManufacturerSpecificCCGet(host, { nodeId: 1 }); + const cc = new ManufacturerSpecificCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ ManufacturerSpecificCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -40,9 +37,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x06, ]), ); - const cc = new ManufacturerSpecificCCReport(host, { + const cc = new ManufacturerSpecificCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.manufacturerId, 0x0102); diff --git a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts index 2587d4b190a7..a335be76bad0 100644 --- a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts @@ -13,7 +13,7 @@ import { ZWaveErrorCodes, assertZWaveError, } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; +import { type GetSupportedCCVersion, createTestingHost } from "@zwave-js/host"; import test from "ava"; import * as nodeUtils from "../../node/utils"; import { createTestNode } from "../mocks"; @@ -31,39 +31,57 @@ const host = createTestingHost(); const node2 = createTestNode(host, { id: 2 }); test("the Get command (V1) should serialize correctly", (t) => { - const cc = new MeterCCGet(host, { nodeId: 1 }); + const cc = new MeterCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 1; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Get command (V2) should serialize correctly", (t) => { - const cc = new MeterCCGet(host, { nodeId: 1, scale: 0x03 }); + const cc = new MeterCCGet({ nodeId: 1, scale: 0x03 }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.Get, // CC Command 0b11_000, // Scale ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 2; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Get command (V3) should serialize correctly", (t) => { - const cc = new MeterCCGet(host, { nodeId: 1, scale: 0x06 }); + const cc = new MeterCCGet({ nodeId: 1, scale: 0x06 }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.Get, // CC Command 0b110_000, // Scale ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 3; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Get command (V4) should serialize correctly", (t) => { - const cc = new MeterCCGet(host, { nodeId: 1, scale: 0x0f }); + const cc = new MeterCCGet({ nodeId: 1, scale: 0x0f }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.Get, // CC Command @@ -71,31 +89,37 @@ test("the Get command (V4) should serialize correctly", (t) => { 0x1, // Scale 2 ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 4; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new MeterCCSupportedGet(host, { nodeId: 1 }); + const cc = new MeterCCSupportedGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Reset command (V2) should serialize correctly", (t) => { - const cc = new MeterCCReset(host, { nodeId: 1 }); + const cc = new MeterCCReset({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ MeterCommand.Reset, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Reset command (V6) should serialize correctly", (t) => { - const cc = new MeterCCReset(host, { + const cc = new MeterCCReset({ nodeId: 1, type: 7, scale: 3, @@ -110,7 +134,7 @@ test("the Reset command (V6) should serialize correctly", (t) => { 123, // 12.3 ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (V1) should be deserialized correctly", (t) => { @@ -122,7 +146,11 @@ test("the Report command (V1) should be deserialized correctly", (t) => { 55, // value ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -143,7 +171,11 @@ test("the Report command (V2) should be deserialized correctly (no time delta)", 0, ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -165,7 +197,11 @@ test("the Report command (V2) should be deserialized correctly (with time delta) 54, // previous value ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -187,7 +223,11 @@ test("the Report command (V3) should be deserialized correctly", (t) => { 54, // previous value ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); t.is(cc.scale, 6); }); @@ -205,7 +245,11 @@ test("the Report command (V4) should be deserialized correctly", (t) => { 0b01, // Scale2 ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); t.is(cc.scale, 8); }); @@ -224,7 +268,11 @@ test("the Report command should validate that a known meter type is given", (t) ]), ); - const report = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const report = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); // Meter type 31 (does not exist) assertZWaveError(t, () => report.persistValues(host), { @@ -246,7 +294,11 @@ test("the Report command should validate that a known meter scale is given", (t) ]), ); - const report = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const report = new MeterCCReport({ + nodeId: 1, + data: ccData, + context: {} as any, + }); // Meter type 4, Scale 8 (does not exist) assertZWaveError(t, () => report.persistValues(host), { @@ -275,9 +327,10 @@ test("the SupportedReport command (V2/V3) should be deserialized correctly", (t) 0b01101110, // supported scales ]), ); - const cc = new MeterCCSupportedReport(host, { + const cc = new MeterCCSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.type, 21); @@ -297,9 +350,10 @@ test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) 1, ]), ); - const cc = new MeterCCSupportedReport(host, { + const cc = new MeterCCSupportedReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.type, 21); @@ -319,7 +373,7 @@ test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) // 1, // ]), // ); -// const cc = new MeterCCSupportedReport(host, { +// const cc = new MeterCCSupportedReport({ // nodeId: 1, // data: ccData, // }); @@ -334,9 +388,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new MeterCC(host, { + const cc: any = new MeterCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, MeterCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts index 4b51e7bdfecd..12db92f59c6f 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts @@ -8,11 +8,8 @@ import { MultiChannelAssociationCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -23,7 +20,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the SupportedGroupingsGet command should serialize correctly", (t) => { - const cc = new MultiChannelAssociationCCSupportedGroupingsGet(host, { + const cc = new MultiChannelAssociationCCSupportedGroupingsGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -31,7 +28,7 @@ test("the SupportedGroupingsGet command should serialize correctly", (t) => { MultiChannelAssociationCommand.SupportedGroupingsGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the SupportedGroupingsReport command should be deserialized correctly", (t) => { @@ -41,16 +38,17 @@ test("the SupportedGroupingsReport command should be deserialized correctly", (t 7, // # of groups ]), ); - const cc = new MultiChannelAssociationCCSupportedGroupingsReport(host, { + const cc = new MultiChannelAssociationCCSupportedGroupingsReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.is(cc.groupCount, 7); }); test("the Set command should serialize correctly (node IDs only)", (t) => { - const cc = new MultiChannelAssociationCCSet(host, { + const cc = new MultiChannelAssociationCCSet({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 5], @@ -65,11 +63,11 @@ test("the Set command should serialize correctly (node IDs only)", (t) => { 5, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (endpoint addresses only)", (t) => { - const cc = new MultiChannelAssociationCCSet(host, { + const cc = new MultiChannelAssociationCCSet({ nodeId: 2, groupId: 5, endpoints: [ @@ -96,11 +94,11 @@ test("the Set command should serialize correctly (endpoint addresses only)", (t) 0b11010111, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (both options)", (t) => { - const cc = new MultiChannelAssociationCCSet(host, { + const cc = new MultiChannelAssociationCCSet({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 3], @@ -132,11 +130,11 @@ test("the Set command should serialize correctly (both options)", (t) => { 0b11010111, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Get command should serialize correctly", (t) => { - const cc = new MultiChannelAssociationCCGet(host, { + const cc = new MultiChannelAssociationCCGet({ nodeId: 1, groupId: 9, }); @@ -146,7 +144,7 @@ test("the Get command should serialize correctly", (t) => { 9, // group ID ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly (node IDs only)", (t) => { @@ -162,9 +160,10 @@ test("the Report command should be deserialized correctly (node IDs only)", (t) 5, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { + const cc = new MultiChannelAssociationCCReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.is(cc.groupId, 5); @@ -190,9 +189,10 @@ test("the Report command should be deserialized correctly (endpoint addresses on 0b11010111, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { + const cc = new MultiChannelAssociationCCReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.deepEqual(cc.nodeIds, []); @@ -227,9 +227,10 @@ test("the Report command should be deserialized correctly (both options)", (t) = 0b11010111, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { + const cc = new MultiChannelAssociationCCReport({ nodeId: 4, data: ccData, + context: {} as any, }); t.deepEqual(cc.nodeIds, [1, 5, 9]); @@ -246,7 +247,7 @@ test("the Report command should be deserialized correctly (both options)", (t) = }); test("the Remove command should serialize correctly (node IDs only)", (t) => { - const cc = new MultiChannelAssociationCCRemove(host, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 5], @@ -261,11 +262,11 @@ test("the Remove command should serialize correctly (node IDs only)", (t) => { 5, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Remove command should serialize correctly (endpoint addresses only)", (t) => { - const cc = new MultiChannelAssociationCCRemove(host, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: 2, groupId: 5, endpoints: [ @@ -292,11 +293,11 @@ test("the Remove command should serialize correctly (endpoint addresses only)", 0b11010111, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Remove command should serialize correctly (both options)", (t) => { - const cc = new MultiChannelAssociationCCRemove(host, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: 2, groupId: 5, nodeIds: [1, 2, 3], @@ -328,11 +329,11 @@ test("the Remove command should serialize correctly (both options)", (t) => { 0b11010111, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Remove command should serialize correctly (both empty)", (t) => { - const cc = new MultiChannelAssociationCCRemove(host, { + const cc = new MultiChannelAssociationCCRemove({ nodeId: 2, groupId: 5, }); @@ -342,7 +343,7 @@ test("the Remove command should serialize correctly (both empty)", (t) => { 5, // group id ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); // test("deserializing an unsupported command should return an unspecified version of MultiChannelAssociationCC", (t) => { @@ -350,7 +351,7 @@ test("the Remove command should serialize correctly (both empty)", (t) => { // 1, // Buffer.from([255]), // not a valid command // ); -// const cc: any = new MultiChannelAssociationCC(host, { +// const cc: any = new MultiChannelAssociationCC({ // data: serializedCC, // }); // t.is(cc.constructor, MultiChannelAssociationCC); diff --git a/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts index 954209ab8e4f..78053cc8e033 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts @@ -16,11 +16,8 @@ import { isEncapsulatingCommandClass, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -31,26 +28,26 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("is an encapsulating CommandClass", (t) => { - let cc: CommandClass = new BasicCCSet(host, { + let cc: CommandClass = new BasicCCSet({ nodeId: 1, targetValue: 50, }); - cc = MultiChannelCC.encapsulate(host, cc); + cc = MultiChannelCC.encapsulate(cc); t.true(isEncapsulatingCommandClass(cc)); }); test("the EndPointGet command should serialize correctly", (t) => { - const cc = new MultiChannelCCEndPointGet(host, { nodeId: 1 }); + const cc = new MultiChannelCCEndPointGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ MultiChannelCommand.EndPointGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CapabilityGet command should serialize correctly", (t) => { - const cc = new MultiChannelCCCapabilityGet(host, { + const cc = new MultiChannelCCCapabilityGet({ nodeId: 2, requestedEndpoint: 7, }); @@ -60,11 +57,11 @@ test("the CapabilityGet command should serialize correctly", (t) => { 7, // EndPoint ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the EndPointFind command should serialize correctly", (t) => { - const cc = new MultiChannelCCEndPointFind(host, { + const cc = new MultiChannelCCEndPointFind({ nodeId: 2, genericClass: 0x01, specificClass: 0x02, @@ -76,16 +73,16 @@ test("the EndPointFind command should serialize correctly", (t) => { 0x02, // specificClass ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CommandEncapsulation command should serialize correctly", (t) => { - let cc: CommandClass = new BasicCCSet(host, { + let cc: CommandClass = new BasicCCSet({ nodeId: 2, targetValue: 5, endpoint: 7, }); - cc = MultiChannelCC.encapsulate(host, cc); + cc = MultiChannelCC.encapsulate(cc); const expected = buildCCBuffer( Buffer.from([ MultiChannelCommand.CommandEncapsulation, // CC Command @@ -96,11 +93,11 @@ test("the CommandEncapsulation command should serialize correctly", (t) => { 5, // target value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the AggregatedMembersGet command should serialize correctly", (t) => { - const cc = new MultiChannelCCAggregatedMembersGet(host, { + const cc = new MultiChannelCCAggregatedMembersGet({ nodeId: 2, requestedEndpoint: 6, }); @@ -110,19 +107,19 @@ test("the AggregatedMembersGet command should serialize correctly", (t) => { 6, // EndPoint ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CommandEncapsulation command should also accept V1CommandEncapsulation as a response", (t) => { // GH#938 - const sent = new MultiChannelCCCommandEncapsulation(host, { + const sent = new MultiChannelCCCommandEncapsulation({ nodeId: 2, destination: 2, - encapsulated: new BasicCCGet(host, { nodeId: 2 }), + encapsulated: new BasicCCGet({ nodeId: 2 }), }); - const received = new MultiChannelCCV1CommandEncapsulation(host, { + const received = new MultiChannelCCV1CommandEncapsulation({ nodeId: 2, - encapsulated: new BasicCCReport(host, { + encapsulated: new BasicCCReport({ nodeId: 2, currentValue: 50, }), @@ -141,7 +138,7 @@ test("the CommandEncapsulation command should also accept V1CommandEncapsulation // 1, // duration // ]), // ); -// const cc = new MultiChannelCCReport(host, { data: ccData }); +// const cc = new MultiChannelCCReport({ data: ccData }); // t.is(cc.currentValue, 55); // t.is(cc.targetValue, 66); @@ -153,9 +150,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new MultiChannelCC(host, { + const cc: any = new MultiChannelCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, MultiChannelCC); }); @@ -188,8 +186,7 @@ test("deserializing an unsupported command should return an unspecified version test("MultiChannelCC/BasicCCGet should expect a response", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: 2, endpoint: 2, }), @@ -199,12 +196,11 @@ test("MultiChannelCC/BasicCCGet should expect a response", (t) => { test("MultiChannelCC/BasicCCGet (multicast) should expect NO response", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: 2, endpoint: 2, }), - ) as MultiChannelCCCommandEncapsulation; + ); // A multicast request never expects a response ccRequest.destination = [1, 2, 3]; t.false(ccRequest.expectsCCResponse()); @@ -212,8 +208,7 @@ test("MultiChannelCC/BasicCCGet (multicast) should expect NO response", (t) => { test("MultiChannelCC/BasicCCSet should expect NO response", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCSet(host, { + new BasicCCSet({ nodeId: 2, endpoint: 2, targetValue: 7, @@ -224,15 +219,13 @@ test("MultiChannelCC/BasicCCSet should expect NO response", (t) => { test("MultiChannelCC/BasicCCGet => MultiChannelCC/BasicCCReport = expected", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: 2, endpoint: 2, }), ); const ccResponse = MultiChannelCC.encapsulate( - host, - new BasicCCReport(host, { + new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }), @@ -244,15 +237,13 @@ test("MultiChannelCC/BasicCCGet => MultiChannelCC/BasicCCReport = expected", (t) test("MultiChannelCC/BasicCCGet => MultiChannelCC/BasicCCGet = unexpected", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: 2, endpoint: 2, }), ); const ccResponse = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: ccRequest.nodeId, endpoint: 2, }), @@ -264,14 +255,13 @@ test("MultiChannelCC/BasicCCGet => MultiChannelCC/BasicCCGet = unexpected", (t) test("MultiChannelCC/BasicCCGet => MultiCommandCC/BasicCCReport = unexpected", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: 2, endpoint: 2, }), ); - const ccResponse = MultiCommandCC.encapsulate(host, [ - new BasicCCReport(host, { + const ccResponse = MultiCommandCC.encapsulate([ + new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }), diff --git a/packages/zwave-js/src/lib/test/cc/MultiCommandCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultiCommandCC.test.ts index 660e6d9abfc2..90aed18f813c 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiCommandCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiCommandCC.test.ts @@ -4,16 +4,13 @@ import { MultiCommandCC, isMultiEncapsulatingCommandClass, } from "@zwave-js/cc"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - test("is a multi-encapsulating CommandClass", (t) => { - let cc: CommandClass = new BasicCCSet(host, { + let cc: CommandClass = new BasicCCSet({ nodeId: 1, targetValue: 50, }); - cc = MultiCommandCC.encapsulate(host, [cc]); + cc = MultiCommandCC.encapsulate([cc]); t.true(isMultiEncapsulatingCommandClass(cc)); }); diff --git a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts index 93b6be08e3f3..2d5f30b129dd 100644 --- a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts @@ -9,11 +9,9 @@ import { MultilevelSwitchCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; +import { type GetSupportedCCVersion } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -24,21 +22,20 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new MultilevelSwitchCCGet(host, { nodeId: 1 }); + const cc = new MultilevelSwitchCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ MultilevelSwitchCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (no duration)", (t) => { - const cc = new MultilevelSwitchCCSet(host, { + const cc = new MultilevelSwitchCCSet({ nodeId: 2, targetValue: 55, }); - cc.version = 1; const expected = buildCCBuffer( Buffer.from([ MultilevelSwitchCommand.Set, // CC Command @@ -46,16 +43,21 @@ test("the Set command should serialize correctly (no duration)", (t) => { 0xff, // default duration ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 1; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Set command (V2) should serialize correctly", (t) => { - const cc = new MultilevelSwitchCCSet(host, { + const cc = new MultilevelSwitchCCSet({ nodeId: 2, targetValue: 55, duration: new Duration(2, "minutes"), }); - cc.version = 2; const expected = buildCCBuffer( Buffer.from([ MultilevelSwitchCommand.Set, // CC Command @@ -63,7 +65,13 @@ test("the Set command (V2) should serialize correctly", (t) => { 0x81, // 2 minutes ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 2; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the Report command (V1) should be deserialized correctly", (t) => { @@ -73,9 +81,10 @@ test("the Report command (V1) should be deserialized correctly", (t) => { 55, // current value ]), ); - const cc = new MultilevelSwitchCCReport(host, { + const cc = new MultilevelSwitchCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.currentValue, 55); @@ -92,9 +101,10 @@ test("the Report command (v4) should be deserialized correctly", (t) => { 1, // duration ]), ); - const cc = new MultilevelSwitchCCReport(host, { + const cc = new MultilevelSwitchCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.currentValue, 55); @@ -104,7 +114,7 @@ test("the Report command (v4) should be deserialized correctly", (t) => { }); test("the StopLevelChange command should serialize correctly", (t) => { - const cc = new MultilevelSwitchCCStopLevelChange(host, { + const cc = new MultilevelSwitchCCStopLevelChange({ nodeId: 1, }); const expected = buildCCBuffer( @@ -112,18 +122,17 @@ test("the StopLevelChange command should serialize correctly", (t) => { MultilevelSwitchCommand.StopLevelChange, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the StartLevelChange command (V2) should serialize correctly (down, ignore start level, with duration)", (t) => { - const cc = new MultilevelSwitchCCStartLevelChange(host, { + const cc = new MultilevelSwitchCCStartLevelChange({ nodeId: 2, direction: "down", ignoreStartLevel: true, startLevel: 50, duration: new Duration(3, "seconds"), }); - cc.version = 2; const expected = buildCCBuffer( Buffer.from([ MultilevelSwitchCommand.StartLevelChange, // CC Command @@ -132,11 +141,17 @@ test("the StartLevelChange command (V2) should serialize correctly (down, ignore 3, // 3 sec ]), ); - t.deepEqual(cc.serialize(), expected); + const ctx = { + getSupportedCCVersion(cc, nodeId, endpointIndex) { + return 2; + }, + } satisfies GetSupportedCCVersion as any; + + t.deepEqual(cc.serialize(ctx), expected); }); test("the SupportedGet command should serialize correctly", (t) => { - const cc = new MultilevelSwitchCCSupportedGet(host, { + const cc = new MultilevelSwitchCCSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -144,16 +159,17 @@ test("the SupportedGet command should serialize correctly", (t) => { MultilevelSwitchCommand.SupportedGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("deserializing an unsupported command should return an unspecified version of MultilevelSwitchCC", (t) => { const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new MultilevelSwitchCC(host, { + const cc: any = new MultilevelSwitchCC({ nodeId: 2, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, MultilevelSwitchCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts b/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts index 48f3900391af..4c2aedc3533e 100644 --- a/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/NoOperationCC.test.ts @@ -1,10 +1,7 @@ import { NoOperationCC } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -15,16 +12,18 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the CC should serialize correctly", (t) => { - const cc = new NoOperationCC(host, { nodeId: 1 }); + const cc = new NoOperationCC({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([]), // No command! ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the CC should be deserialized correctly", (t) => { const ccData = buildCCBuffer( Buffer.from([]), // No command! ); - t.notThrows(() => new NoOperationCC(host, { nodeId: 2, data: ccData })); + t.notThrows(() => + new NoOperationCC({ nodeId: 2, data: ccData, context: {} as any }) + ); }); diff --git a/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts b/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts index 8f1e9fd10aea..d847a7b75013 100644 --- a/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts @@ -7,11 +7,8 @@ import { PowerlevelCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -22,17 +19,17 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new PowerlevelCCGet(host, { nodeId: 1 }); + const cc = new PowerlevelCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ PowerlevelCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set NormalPower command should serialize correctly", (t) => { - const cc = new PowerlevelCCSet(host, { + const cc = new PowerlevelCCSet({ nodeId: 2, powerlevel: Powerlevel["Normal Power"], }); @@ -43,11 +40,11 @@ test("the Set NormalPower command should serialize correctly", (t) => { 0, // timeout (ignored) ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set NormalPower command with timeout should serialize correctly", (t) => { - const cc = new PowerlevelCCSet(host, { + const cc = new PowerlevelCCSet({ nodeId: 2, powerlevel: Powerlevel["Normal Power"], timeout: 50, @@ -59,11 +56,11 @@ test("the Set NormalPower command with timeout should serialize correctly", (t) 0x00, // timeout ignored ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set Custom power command should serialize correctly", (t) => { - const cc = new PowerlevelCCSet(host, { + const cc = new PowerlevelCCSet({ nodeId: 2, powerlevel: Powerlevel["-1 dBm"], timeout: 50, @@ -75,7 +72,7 @@ test("the Set Custom power command should serialize correctly", (t) => { 50, // timeout ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command should be deserialized correctly (NormalPower)", (t) => { @@ -86,9 +83,10 @@ test("the Report command should be deserialized correctly (NormalPower)", (t) => 50, // timeout (ignored because NormalPower) ]), ); - const cc = new PowerlevelCCReport(host, { + const cc = new PowerlevelCCReport({ nodeId: 5, data: ccData, + context: {} as any, }); t.is(cc.powerlevel, Powerlevel["Normal Power"]); @@ -103,9 +101,10 @@ test("the Report command should be deserialized correctly (custom power)", (t) = 50, // timeout (ignored because NormalPower) ]), ); - const cc = new PowerlevelCCReport(host, { + const cc = new PowerlevelCCReport({ nodeId: 5, data: ccData, + context: {} as any, }); t.is(cc.powerlevel, Powerlevel["-3 dBm"]); @@ -116,9 +115,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new PowerlevelCC(host, { + const cc: any = new PowerlevelCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, PowerlevelCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts index e9180c52f289..4520223956af 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts @@ -4,11 +4,8 @@ import { SceneActivationCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -19,7 +16,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Set command (without Duration) should serialize correctly", (t) => { - const cc = new SceneActivationCCSet(host, { + const cc = new SceneActivationCCSet({ nodeId: 2, sceneId: 55, }); @@ -30,11 +27,11 @@ test("the Set command (without Duration) should serialize correctly", (t) => { 0xff, // default duration ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command (with Duration) should serialize correctly", (t) => { - const cc = new SceneActivationCCSet(host, { + const cc = new SceneActivationCCSet({ nodeId: 2, sceneId: 56, dimmingDuration: new Duration(1, "minutes"), @@ -46,7 +43,7 @@ test("the Set command (with Duration) should serialize correctly", (t) => { 0x80, // 1 minute ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should be deserialized correctly", (t) => { @@ -57,9 +54,10 @@ test("the Set command should be deserialized correctly", (t) => { 0x00, // 0 seconds ]), ); - const cc = new SceneActivationCCSet(host, { + const cc = new SceneActivationCCSet({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.sceneId, 15); @@ -70,9 +68,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new SceneActivationCC(host, { + const cc: any = new SceneActivationCC({ nodeId: 2, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, SceneActivationCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts index 399f0d56083e..7270aef3ee8a 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts @@ -6,11 +6,8 @@ import { SceneActuatorConfigurationCommand, } from "@zwave-js/cc"; import { CommandClasses, Duration } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -21,7 +18,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new SceneActuatorConfigurationCCGet(host, { + const cc = new SceneActuatorConfigurationCCGet({ nodeId: 2, sceneId: 1, }); @@ -31,11 +28,11 @@ test("the Get command should serialize correctly", (t) => { 1, ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly with level", (t) => { - const cc = new SceneActuatorConfigurationCCSet(host, { + const cc = new SceneActuatorConfigurationCCSet({ nodeId: 2, sceneId: 2, level: 0x00, @@ -50,11 +47,11 @@ test("the Set command should serialize correctly with level", (t) => { 0x00, // level ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly with undefined level", (t) => { - const cc = new SceneActuatorConfigurationCCSet(host, { + const cc = new SceneActuatorConfigurationCCSet({ nodeId: 2, sceneId: 2, // level: undefined, @@ -69,7 +66,7 @@ test("the Set command should serialize correctly with undefined level", (t) => { 0xff, // level ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { @@ -81,9 +78,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x05, // dimmingDuration ]), ); - const cc = new SceneActuatorConfigurationCCReport(host, { + const cc = new SceneActuatorConfigurationCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.sceneId, 55); @@ -95,9 +93,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new SceneActuatorConfigurationCC(host, { + const cc: any = new SceneActuatorConfigurationCC({ nodeId: 2, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, SceneActuatorConfigurationCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts b/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts index d28b30f26ce2..ff76ea6285ec 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts @@ -5,11 +5,8 @@ import { SceneControllerConfigurationCCSet, SceneControllerConfigurationCommand, } from "@zwave-js/cc"; -import { AssociationCCValues } from "@zwave-js/cc/AssociationCC"; -import { CommandClasses, Duration, type IZWaveNode } from "@zwave-js/core"; -import { type TestingHost, createTestingHost } from "@zwave-js/host"; +import { CommandClasses, Duration } from "@zwave-js/core"; import test from "ava"; -import { createTestNode } from "../mocks"; function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ @@ -20,21 +17,8 @@ function buildCCBuffer(payload: Buffer): Buffer { ]); } -const fakeGroupCount = 5; -const groupCountValueId = AssociationCCValues.groupCount.id; - -function prepareTest(): { host: TestingHost; node2: IZWaveNode } { - const host = createTestingHost(); - const node2 = createTestNode(host, { id: 2 }); - host.nodes.set(2, node2); - host.getValueDB(2).setValue(groupCountValueId, fakeGroupCount); - - return { host, node2 }; -} - test("the Get command should serialize correctly", (t) => { - const { host } = prepareTest(); - const cc = new SceneControllerConfigurationCCGet(host, { + const cc = new SceneControllerConfigurationCCGet({ nodeId: 2, groupId: 1, }); @@ -44,23 +28,11 @@ test("the Get command should serialize correctly", (t) => { 0b0000_0001, ]), ); - t.deepEqual(cc.serialize(), expected); -}); - -test.skip("the Get command should throw if GroupId > groupCount", (t) => { - const { host } = prepareTest(); - // TODO: This check now lives on the CC API - t.notThrows(() => { - new SceneControllerConfigurationCCGet(host, { - nodeId: 2, - groupId: fakeGroupCount + 1, - }); - }); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly", (t) => { - const { host } = prepareTest(); - const cc = new SceneControllerConfigurationCCSet(host, { + const cc = new SceneControllerConfigurationCCSet({ nodeId: 2, groupId: 3, sceneId: 240, @@ -74,12 +46,11 @@ test("the Set command should serialize correctly", (t) => { 0x05, // dimming duration ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly with undefined duration", (t) => { - const { host } = prepareTest(); - const cc = new SceneControllerConfigurationCCSet(host, { + const cc = new SceneControllerConfigurationCCSet({ nodeId: 2, groupId: 3, sceneId: 240, @@ -93,25 +64,10 @@ test("the Set command should serialize correctly with undefined duration", (t) = 0xff, // dimming duration ]), ); - t.deepEqual(cc.serialize(), expected); -}); - -test.skip("the Set command should throw if GroupId > groupCount", (t) => { - const { host } = prepareTest(); - // TODO: This check now lives on the CC API - t.notThrows( - () => - new SceneControllerConfigurationCCSet(host, { - nodeId: 2, - groupId: fakeGroupCount + 1, - sceneId: 240, - dimmingDuration: Duration.parseSet(0x05)!, - }), - ); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1) should be deserialized correctly", (t) => { - const { host } = prepareTest(); const ccData = buildCCBuffer( Buffer.from([ SceneControllerConfigurationCommand.Report, // CC Command @@ -120,9 +76,10 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x05, // dimming duration ]), ); - const cc = new SceneControllerConfigurationCCReport(host, { + const cc = new SceneControllerConfigurationCCReport({ nodeId: 2, data: ccData, + context: {} as any, }); t.is(cc.groupId, 3); @@ -131,13 +88,13 @@ test("the Report command (v1) should be deserialized correctly", (t) => { }); test("deserializing an unsupported command should return an unspecified version of SceneControllerConfigurationCC", (t) => { - const { host } = prepareTest(); const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new SceneControllerConfigurationCC(host, { + const cc: any = new SceneControllerConfigurationCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, SceneControllerConfigurationCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/SupervisionCC.test.ts b/packages/zwave-js/src/lib/test/cc/SupervisionCC.test.ts index 2e0be00a4c6c..155f7bc9025a 100644 --- a/packages/zwave-js/src/lib/test/cc/SupervisionCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SupervisionCC.test.ts @@ -1,30 +1,27 @@ import { BasicCCSet, SupervisionCC, SupervisionCCReport } from "@zwave-js/cc"; import { SupervisionStatus } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - test("SupervisionCCGet should expect a response", (t) => { const ccRequest = SupervisionCC.encapsulate( - host, - new BasicCCSet(host, { + new BasicCCSet({ nodeId: 2, targetValue: 5, }), + 1, ); t.true(ccRequest.expectsCCResponse()); }); test("SupervisionCC/BasicCCSet => SupervisionCCReport (correct session ID) = expected", (t) => { const ccRequest = SupervisionCC.encapsulate( - host, - new BasicCCSet(host, { + new BasicCCSet({ nodeId: 2, targetValue: 5, }), + 2, ); - const ccResponse = new SupervisionCCReport(host, { + const ccResponse = new SupervisionCCReport({ nodeId: 2, moreUpdatesFollow: false, sessionId: ccRequest.sessionId, @@ -36,13 +33,13 @@ test("SupervisionCC/BasicCCSet => SupervisionCCReport (correct session ID) = exp test("SupervisionCC/BasicCCSet => SupervisionCCReport (wrong session ID) = unexpected", (t) => { const ccRequest = SupervisionCC.encapsulate( - host, - new BasicCCSet(host, { + new BasicCCSet({ nodeId: 2, targetValue: 5, }), + 3, ); - const ccResponse = new SupervisionCCReport(host, { + const ccResponse = new SupervisionCCReport({ nodeId: 2, moreUpdatesFollow: false, sessionId: ccRequest.sessionId + 1, diff --git a/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts b/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts index 1e599143259c..72cc8c72bef7 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts @@ -6,11 +6,8 @@ import { ThermostatFanModeCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -21,17 +18,17 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new ThermostatFanModeCCGet(host, { nodeId: 5 }); + const cc = new ThermostatFanModeCCGet({ nodeId: 5 }); const expected = buildCCBuffer( Buffer.from([ ThermostatFanModeCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (off = false)", (t) => { - const cc = new ThermostatFanModeCCSet(host, { + const cc = new ThermostatFanModeCCSet({ nodeId: 5, mode: ThermostatFanMode["Auto medium"], off: false, @@ -42,11 +39,11 @@ test("the Set command should serialize correctly (off = false)", (t) => { 0x04, // target value ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Set command should serialize correctly (off = true)", (t) => { - const cc = new ThermostatFanModeCCSet(host, { + const cc = new ThermostatFanModeCCSet({ nodeId: 5, mode: ThermostatFanMode["Auto medium"], off: true, @@ -57,41 +54,20 @@ test("the Set command should serialize correctly (off = true)", (t) => { 0b1000_0100, // target value ]), ); - t.deepEqual(cc.serialize(), expected); -}); - -test("the Report command (v1-v2) should be deserialized correctly", (t) => { - const ccData = buildCCBuffer( - Buffer.from([ - ThermostatFanModeCommand.Report, // CC Command - ThermostatFanMode["Auto low"], // current value - ]), - ); - const cc = new ThermostatFanModeCCReport( - { - ...host, - getSafeCCVersion: () => 1, - }, - { - nodeId: 1, - data: ccData, - }, - ); - - t.is(cc.mode, ThermostatFanMode["Auto low"]); - t.is(cc.off, undefined); + t.deepEqual(cc.serialize({} as any), expected); }); -test("the Report command (v3-v5) should be deserialized correctly", (t) => { +test("the Report command should be deserialized correctly", (t) => { const ccData = buildCCBuffer( Buffer.from([ ThermostatFanModeCommand.Report, // CC Command 0b1000_0010, // Off bit set to 1 and Auto high mode ]), ); - const cc = new ThermostatFanModeCCReport(host, { + const cc = new ThermostatFanModeCCReport({ nodeId: 5, data: ccData, + context: {} as any, }); t.is(cc.mode, ThermostatFanMode["Auto high"]); diff --git a/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts b/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts index 7c90dca3e40e..03ff89fe8d98 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts @@ -6,11 +6,8 @@ import { ThermostatFanStateCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -21,13 +18,13 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the Get command should serialize correctly", (t) => { - const cc = new ThermostatFanStateCCGet(host, { nodeId: 1 }); + const cc = new ThermostatFanStateCCGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ ThermostatFanStateCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the Report command (v1 - v2) should be deserialized correctly", (t) => { @@ -37,9 +34,10 @@ test("the Report command (v1 - v2) should be deserialized correctly", (t) => { ThermostatFanState["Idle / off"], // state ]), ); - const cc = new ThermostatFanStateCCReport(host, { + const cc = new ThermostatFanStateCCReport({ nodeId: 1, data: ccData, + context: {} as any, }); t.is(cc.state, ThermostatFanState["Idle / off"]); @@ -49,9 +47,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new ThermostatFanStateCC(host, { + const cc: any = new ThermostatFanStateCC({ nodeId: 1, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, ThermostatFanStateCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts b/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts index 0b204d306502..8635a5ec304e 100644 --- a/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts @@ -7,11 +7,8 @@ import { TimeCommand, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -22,13 +19,13 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("the TimeGet command should serialize correctly", (t) => { - const cc = new TimeCCTimeGet(host, { nodeId: 1 }); + const cc = new TimeCCTimeGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ TimeCommand.TimeGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the TimeReport command should be deserialized correctly", (t) => { @@ -40,9 +37,10 @@ test("the TimeReport command should be deserialized correctly", (t) => { 59, ]), ); - const cc = new TimeCCTimeReport(host, { + const cc = new TimeCCTimeReport({ nodeId: 8, data: ccData, + context: {} as any, }); t.is(cc.hour, 14); @@ -51,13 +49,13 @@ test("the TimeReport command should be deserialized correctly", (t) => { }); test("the DateGet command should serialize correctly", (t) => { - const cc = new TimeCCDateGet(host, { nodeId: 1 }); + const cc = new TimeCCDateGet({ nodeId: 1 }); const expected = buildCCBuffer( Buffer.from([ TimeCommand.DateGet, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); test("the DateReport command should be deserialized correctly", (t) => { @@ -70,9 +68,10 @@ test("the DateReport command should be deserialized correctly", (t) => { 17, ]), ); - const cc = new TimeCCDateReport(host, { + const cc = new TimeCCDateReport({ nodeId: 8, data: ccData, + context: {} as any, }); t.is(cc.year, 1989); @@ -84,9 +83,10 @@ test("deserializing an unsupported command should return an unspecified version const serializedCC = buildCCBuffer( Buffer.from([255]), // not a valid command ); - const cc: any = new TimeCC(host, { + const cc: any = new TimeCC({ nodeId: 8, data: serializedCC, + context: {} as any, }); t.is(cc.constructor, TimeCC); }); diff --git a/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts b/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts index f81708cf33ef..6de0e0e34cb0 100644 --- a/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts @@ -4,14 +4,11 @@ import { WakeUpCCNoMoreInformation, } from "@zwave-js/cc"; import { generateAuthKey, generateEncryptionKey } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; import { randomBytes } from "node:crypto"; -const host = createTestingHost(); - test("WakeUpCCNoMoreInformation should expect no response", (t) => { - const cc = new WakeUpCCNoMoreInformation(host, { + const cc = new WakeUpCCNoMoreInformation({ nodeId: 2, endpoint: 2, }); @@ -20,8 +17,7 @@ test("WakeUpCCNoMoreInformation should expect no response", (t) => { test("MultiChannelCC/WakeUpCCNoMoreInformation should expect NO response", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new WakeUpCCNoMoreInformation(host, { + new WakeUpCCNoMoreInformation({ nodeId: 2, endpoint: 2, }), @@ -42,11 +38,9 @@ test("SecurityCC/WakeUpCCNoMoreInformation should expect NO response", (t) => { }; const ccRequest = SecurityCC.encapsulate( - { - ...host, - securityManager, - } as any, - new WakeUpCCNoMoreInformation(host, { + 1, + securityManager as any, + new WakeUpCCNoMoreInformation({ nodeId: 2, endpoint: 2, }), diff --git a/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts b/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts index 066c2aa59612..4933a967c143 100644 --- a/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ZWavePlusCC.test.ts @@ -1,10 +1,7 @@ import { ZWavePlusCCGet, ZWavePlusCommand } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; import test from "ava"; -const host = createTestingHost(); - function buildCCBuffer(payload: Buffer): Buffer { return Buffer.concat([ Buffer.from([ @@ -15,7 +12,7 @@ function buildCCBuffer(payload: Buffer): Buffer { } test("The Get command should serialize correctly", (t) => { - const cc = new ZWavePlusCCGet(host, { + const cc = new ZWavePlusCCGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -23,7 +20,7 @@ test("The Get command should serialize correctly", (t) => { ZWavePlusCommand.Get, // CC Command ]), ); - t.deepEqual(cc.serialize(), expected); + t.deepEqual(cc.serialize({} as any), expected); }); // describe.skip(`interview()`, () => { diff --git a/packages/zwave-js/src/lib/test/compat/binarySensorReportAnyUseFirstSupported.test.ts b/packages/zwave-js/src/lib/test/compat/binarySensorReportAnyUseFirstSupported.test.ts index 063e5f569ba6..456fbdbe0297 100644 --- a/packages/zwave-js/src/lib/test/compat/binarySensorReportAnyUseFirstSupported.test.ts +++ b/packages/zwave-js/src/lib/test/compat/binarySensorReportAnyUseFirstSupported.test.ts @@ -39,8 +39,8 @@ integrationTest( }; // Incorrectly respond with 0xFF as the sensor type - const cc = new BinarySensorCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySensorCCReport({ + nodeId: controller.ownNodeId, type: BinarySensorType.Any, value: true, }); diff --git a/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts b/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts index 5e5d05951f9e..502a31cfc079 100644 --- a/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts +++ b/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts @@ -44,7 +44,7 @@ integrationTest( customSetup: async (driver, controller, mockNode) => { // Incorrectly respond to AssignSUCReturnRoute with DeleteSUCReturnRoute const handleAssignSUCReturnRoute: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof AssignSUCReturnRouteRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -65,15 +65,14 @@ integrationTest( MockControllerCommunicationState.Sending, ); - const expectCallback = msg.callbackId !== 0; + const expectCallback = !!msg.callbackId; // Send the command to the node const node = controller.nodes.get(msg.getNodeId()!)!; const command = new ZWaveProtocolCCAssignSUCReturnRoute( - host, { nodeId: node.id, - destinationNodeId: controller.host.ownNodeId, + destinationNodeId: controller.ownNodeId, repeaters: [], // don't care routeIndex: 0, // don't care destinationSpeed: ZWaveDataRate["100k"], @@ -86,10 +85,10 @@ integrationTest( const ackPromise = controller.sendToNode(node, frame); // Notify the host that the message was sent - const res = new AssignSUCReturnRouteResponse(host, { + const res = new AssignSUCReturnRouteResponse({ wasExecuted: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); let ack = false; if (expectCallback) { @@ -114,17 +113,14 @@ integrationTest( if (expectCallback) { const cb = - new DeleteSUCReturnRouteRequestTransmitReport( - host, - { - callbackId: msg.callbackId, - transmitStatus: ack - ? TransmitStatus.OK - : TransmitStatus.NoAck, - }, - ); - - await controller.sendToHost(cb.serialize()); + new DeleteSUCReturnRouteRequestTransmitReport({ + callbackId: msg.callbackId!, + transmitStatus: ack + ? TransmitStatus.OK + : TransmitStatus.NoAck, + }); + + await controller.sendMessageToHost(cb); } return true; } @@ -134,7 +130,7 @@ integrationTest( // Incorrectly respond to DeleteSUCReturnRoute with a message with function type 0 const handleDeleteSUCReturnRoute: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof DeleteSUCReturnRouteRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -155,15 +151,14 @@ integrationTest( MockControllerCommunicationState.Sending, ); - const expectCallback = msg.callbackId !== 0; + const expectCallback = !!msg.callbackId; // Send the command to the node const node = controller.nodes.get(msg.getNodeId()!)!; const command = new ZWaveProtocolCCAssignSUCReturnRoute( - host, { nodeId: node.id, - destinationNodeId: controller.host.ownNodeId, + destinationNodeId: controller.ownNodeId, repeaters: [], // don't care routeIndex: 0, // don't care destinationSpeed: ZWaveDataRate["100k"], @@ -176,10 +171,10 @@ integrationTest( const ackPromise = controller.sendToNode(node, frame); // Notify the host that the message was sent - const res = new DeleteSUCReturnRouteResponse(host, { + const res = new DeleteSUCReturnRouteResponse({ wasExecuted: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); let ack = false; if (expectCallback) { @@ -204,19 +199,16 @@ integrationTest( if (expectCallback) { const cb = - new DeleteSUCReturnRouteRequestTransmitReport( - host, - { - callbackId: msg.callbackId, - transmitStatus: ack - ? TransmitStatus.OK - : TransmitStatus.NoAck, - }, - ); + new DeleteSUCReturnRouteRequestTransmitReport({ + callbackId: msg.callbackId!, + transmitStatus: ack + ? TransmitStatus.OK + : TransmitStatus.NoAck, + }); // @ts-expect-error 0 is not a valid function type cb.functionType = 0; - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } return true; } diff --git a/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts b/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts index f6efbff44482..e0b90d97733b 100644 --- a/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts +++ b/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts @@ -31,7 +31,7 @@ integrationTest( async testBody(t, driver, node, mockController, mockNode) { // Send a report that should be mapped to notifications - const cc = new NotificationCCReport(mockNode.host, { + const cc = new NotificationCCReport({ nodeId: 2, alarmType: 18, alarmLevel: 2, diff --git a/packages/zwave-js/src/lib/test/compat/reInterviewWakeUpNIF.test.ts b/packages/zwave-js/src/lib/test/compat/reInterviewWakeUpNIF.test.ts index 4fa3c8a31040..f124ca57ec5e 100644 --- a/packages/zwave-js/src/lib/test/compat/reInterviewWakeUpNIF.test.ts +++ b/packages/zwave-js/src/lib/test/compat/reInterviewWakeUpNIF.test.ts @@ -31,7 +31,7 @@ integrationTest( await wait(500); // Send a NIF to trigger the re-interview - const cc = new ZWaveProtocolCCNodeInformationFrame(mockNode.host, { + const cc = new ZWaveProtocolCCNodeInformationFrame({ nodeId: mockNode.id, ...mockNode.capabilities, supportedCCs: [...mockNode.implementedCCs] diff --git a/packages/zwave-js/src/lib/test/compliance/decodeLowerS2Keys.test.ts b/packages/zwave-js/src/lib/test/compliance/decodeLowerS2Keys.test.ts index c299f7530787..a59b1b455a24 100644 --- a/packages/zwave-js/src/lib/test/compliance/decodeLowerS2Keys.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/decodeLowerS2Keys.test.ts @@ -47,20 +47,42 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = sm2Node; - // The fixtures define Access Control as granted, but we want the node to send commands using Unauthenticated - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = sm2Node; + // The fixtures define Access Control as granted, but we want the prode to send commands using Unauthenticated + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; + // Create a security manager for the controller + const smCtrlr = new SecurityManager2(); + // Copy keys from the driver + smCtrlr.setKey( + SecurityClass.S2_AccessControl, + driver.options.securityKeys!.S2_AccessControl!, + ); + smCtrlr.setKey( + SecurityClass.S2_Authenticated, + driver.options.securityKeys!.S2_Authenticated!, + ); + smCtrlr.setKey( + SecurityClass.S2_Unauthenticated, + driver.options.securityKeys!.S2_Unauthenticated!, + ); + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; + // Respond to S2 Nonce Get const respondToS2NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = sm2Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -79,8 +101,8 @@ integrationTest( type: SPANState.LocalEI, receiverEI: controllerEI, }); - mockNode.host.securityManager2!.setSPANState( - mockController.host.ownNodeId, + mockNode.securityManagers.securityManager2!.setSPANState( + mockController.ownNodeId, { type: SPANState.RemoteEI, receiverEI: controllerEI, @@ -88,12 +110,14 @@ integrationTest( ); // The node sends an S2-encapsulated command, but with a lower security class than expected - let innerCC: CommandClass = new TimeCCTimeGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let innerCC: CommandClass = new TimeCCTimeGet({ + nodeId: mockController.ownNodeId, }); - let cc = new Security2CCMessageEncapsulation(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new Security2CCMessageEncapsulation({ + nodeId: mockController.ownNodeId, + ownNodeId: mockNode.id, encapsulated: innerCC, + securityManagers: mockNode.securityManagers, }); await mockNode.sendToController( @@ -129,13 +153,15 @@ integrationTest( mockNode.clearReceivedControllerFrames(); // Now the node queries our securely supported commands - innerCC = new Security2CCCommandsSupportedGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + innerCC = new Security2CCCommandsSupportedGet({ + nodeId: mockController.ownNodeId, }); - cc = new Security2CCMessageEncapsulation(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new Security2CCMessageEncapsulation({ + nodeId: mockController.ownNodeId, + ownNodeId: mockNode.id, encapsulated: innerCC, + securityManagers: mockNode.securityManagers, }); await mockNode.sendToController( diff --git a/packages/zwave-js/src/lib/test/compliance/discardInsecureCommands.test.ts b/packages/zwave-js/src/lib/test/compliance/discardInsecureCommands.test.ts index 47a527cda2f9..32612ed3beab 100644 --- a/packages/zwave-js/src/lib/test/compliance/discardInsecureCommands.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/discardInsecureCommands.test.ts @@ -44,8 +44,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -63,19 +63,22 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -97,10 +100,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -123,11 +128,12 @@ integrationTest( // Send a secure command that should be handled let nodeToHost: CommandClass = Security2CC.encapsulate( - mockNode.host, - new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 99, }), + mockNode.id, + mockNode.securityManagers, ); await mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { @@ -142,8 +148,8 @@ integrationTest( t.is(currentValue, 99); // Then send an unencypted one that should be discarded - nodeToHost = new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + nodeToHost = new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 1, }); diff --git a/packages/zwave-js/src/lib/test/compliance/encapsulationAnswerAsAsked.test.ts b/packages/zwave-js/src/lib/test/compliance/encapsulationAnswerAsAsked.test.ts index 3463762265f6..4fd87ee2aa53 100644 --- a/packages/zwave-js/src/lib/test/compliance/encapsulationAnswerAsAsked.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/encapsulationAnswerAsAsked.test.ts @@ -41,10 +41,10 @@ integrationTest( testBody: async (t, driver, node, mockController, mockNode) => { // We know that the driver must respond to Z-Wave Plus Info Get // so we can use that to test - const zwpRequest = new ZWavePlusCCGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const zwpRequest = new ZWavePlusCCGet({ + nodeId: mockController.ownNodeId, }); - const cc = CRC16CC.encapsulate(mockNode.host, zwpRequest); + const cc = CRC16CC.encapsulate(zwpRequest); await mockNode.sendToController(createMockZWaveRequestFrame(cc)); const { payload: response } = await mockNode.expectControllerFrame( @@ -88,11 +88,11 @@ integrationTest( testBody: async (t, driver, node, mockController, mockNode) => { // We know that the driver must respond to Z-Wave Plus Info Get // so we can use that to test - const zwpRequest = new ZWavePlusCCGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const zwpRequest = new ZWavePlusCCGet({ + nodeId: mockController.ownNodeId, }); - const cc = MultiChannelCC.encapsulate(mockNode.host, zwpRequest); - (cc as MultiChannelCCCommandEncapsulation).endpointIndex = 2; + const cc = MultiChannelCC.encapsulate(zwpRequest); + cc.endpointIndex = 2; await mockNode.sendToController(createMockZWaveRequestFrame(cc)); @@ -138,11 +138,14 @@ integrationTest( }, testBody: async (t, driver, node, mockController, mockNode) => { - const basicReport = new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const basicReport = new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 0, }); - const cc = SupervisionCC.encapsulate(mockNode.host, basicReport); + const cc = SupervisionCC.encapsulate( + basicReport, + driver.getNextSupervisionSessionId(mockNode.id), + ); await mockNode.sendToController(createMockZWaveRequestFrame(cc)); @@ -189,16 +192,16 @@ integrationTest( }, testBody: async (t, driver, node, mockController, mockNode) => { - const basicReport = new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const basicReport = new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 0, }); const supervised = SupervisionCC.encapsulate( - mockNode.host, basicReport, + driver.getNextSupervisionSessionId(mockNode.id), ); - const cc = MultiChannelCC.encapsulate(mockNode.host, supervised); - (cc as MultiChannelCCCommandEncapsulation).endpointIndex = 2; + const cc = MultiChannelCC.encapsulate(supervised); + cc.endpointIndex = 2; await mockNode.sendToController(createMockZWaveRequestFrame(cc)); diff --git a/packages/zwave-js/src/lib/test/compliance/fixtures/handleMultiCommandPayload/7e570001.jsonl b/packages/zwave-js/src/lib/test/compliance/fixtures/handleMultiCommandPayload/7e570001.jsonl index 4423f128d324..65ac64955c4e 100644 --- a/packages/zwave-js/src/lib/test/compliance/fixtures/handleMultiCommandPayload/7e570001.jsonl +++ b/packages/zwave-js/src/lib/test/compliance/fixtures/handleMultiCommandPayload/7e570001.jsonl @@ -17,3 +17,4 @@ // Define some base functionality for node 2 so we can skip the interview of these CCs {"k":"node.2.endpoint.0.commandClass.0x5e","v":{"isSupported":true,"isControlled":false,"secure":false,"version":2}} +{"k":"node.2.endpoint.0.commandClass.0x2b","v":{"isSupported":false,"isControlled":true,"secure":false,"version":1}} diff --git a/packages/zwave-js/src/lib/test/compliance/handleMultiCommandPayload.test.ts b/packages/zwave-js/src/lib/test/compliance/handleMultiCommandPayload.test.ts index e2aa64e62b84..0e86c08eb1c1 100644 --- a/packages/zwave-js/src/lib/test/compliance/handleMultiCommandPayload.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/handleMultiCommandPayload.test.ts @@ -31,15 +31,15 @@ integrationTest("All CCs contained in a Multi Command CC are handled", { testBody: async (t, driver, node, mockController, mockNode) => { // This one requires a response - const zwpRequest = new ZWavePlusCCGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const zwpRequest = new ZWavePlusCCGet({ + nodeId: mockController.ownNodeId, }); // This one updates a value - const scaSet = new SceneActivationCCSet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const scaSet = new SceneActivationCCSet({ + nodeId: mockController.ownNodeId, sceneId: 7, }); - const cc = MultiCommandCC.encapsulate(mockNode.host, [ + const cc = MultiCommandCC.encapsulate([ zwpRequest, scaSet, ]); diff --git a/packages/zwave-js/src/lib/test/compliance/secureNodeSecureEndpoint.test.ts b/packages/zwave-js/src/lib/test/compliance/secureNodeSecureEndpoint.test.ts index f05e0fa95292..45c276005024 100644 --- a/packages/zwave-js/src/lib/test/compliance/secureNodeSecureEndpoint.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/secureNodeSecureEndpoint.test.ts @@ -95,8 +95,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -114,19 +114,22 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -148,10 +151,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -173,12 +178,13 @@ integrationTest( instanceof Security2CCCommandsSupportedGet ) { const isHighestGranted = receivedCC.securityClass - === self.host.getHighestSecurityClass(self.id); + === self.encodingContext.getHighestSecurityClass( + self.id, + ); const cc = Security2CC.encapsulate( - self.host, - new Security2CCCommandsSupportedReport(self.host, { - nodeId: controller.host.ownNodeId, + new Security2CCCommandsSupportedReport({ + nodeId: controller.ownNodeId, supportedCCs: isHighestGranted ? [...mockNode.implementedCCs.entries()] .filter( @@ -192,6 +198,8 @@ integrationTest( .map(([ccId]) => ccId) : [], }), + self.id, + self.securityManagers, ); return { action: "sendCC", cc }; } @@ -208,13 +216,14 @@ integrationTest( instanceof MultiChannelCCEndPointGet ) { const cc = Security2CC.encapsulate( - self.host, - new MultiChannelCCEndPointReport(self.host, { - nodeId: controller.host.ownNodeId, + new MultiChannelCCEndPointReport({ + nodeId: controller.ownNodeId, countIsDynamic: false, identicalCapabilities: false, individualCount: self.endpoints.size, }), + self.id, + self.securityManagers, ); return { action: "sendCC", cc }; } @@ -232,14 +241,15 @@ integrationTest( ) { const request = receivedCC.encapsulated; const cc = Security2CC.encapsulate( - self.host, - new MultiChannelCCEndPointFindReport(self.host, { - nodeId: controller.host.ownNodeId, + new MultiChannelCCEndPointFindReport({ + nodeId: controller.ownNodeId, genericClass: request.genericClass, specificClass: request.specificClass, foundEndpoints: [...self.endpoints.keys()], reportsToFollow: 0, }), + self.id, + self.securityManagers, ); return { action: "sendCC", cc }; } @@ -259,9 +269,8 @@ integrationTest( receivedCC.encapsulated.requestedEndpoint, )!; const cc = Security2CC.encapsulate( - self.host, - new MultiChannelCCCapabilityReport(self.host, { - nodeId: controller.host.ownNodeId, + new MultiChannelCCCapabilityReport({ + nodeId: controller.ownNodeId, endpointIndex: endpoint.index, genericDeviceClass: endpoint?.capabilities.genericDeviceClass @@ -275,6 +284,8 @@ integrationTest( ...endpoint.implementedCCs.keys(), ], }), + self.id, + self.securityManagers, ); return { action: "sendCC", cc }; } diff --git a/packages/zwave-js/src/lib/test/compliance/zwavePlusInfoResponse.test.ts b/packages/zwave-js/src/lib/test/compliance/zwavePlusInfoResponse.test.ts index 5ec609b59f43..d1cd190b1ad3 100644 --- a/packages/zwave-js/src/lib/test/compliance/zwavePlusInfoResponse.test.ts +++ b/packages/zwave-js/src/lib/test/compliance/zwavePlusInfoResponse.test.ts @@ -27,7 +27,7 @@ integrationTest("Response to Z-Wave Plus Info Get", { }, testBody: async (t, driver, node, mockController, mockNode) => { - const zwpRequest = new ZWavePlusCCGet(mockController.host, { + const zwpRequest = new ZWavePlusCCGet({ nodeId: mockNode.id, }); await mockNode.sendToController( diff --git a/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts b/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts index f3be78f262a8..b63ee216c405 100644 --- a/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts @@ -50,8 +50,8 @@ test.afterEach.always(async (t) => { test.serial("returns true when a non-partial CC is received", (t) => { const { driver } = t.context; - const cc = new BasicCCSet(driver, { nodeId: 2, targetValue: 50 }); - const msg = new ApplicationCommandRequest(driver, { + const cc = new BasicCCSet({ nodeId: 2, targetValue: 50 }); + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -61,7 +61,7 @@ test.serial( "returns true when a partial CC is received that expects no more reports", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -73,8 +73,9 @@ test.serial( 2, 3, ]), + context: {} as any, }); - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -85,7 +86,7 @@ test.serial( "returns false when a partial CC is received that expects more reports", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -97,8 +98,9 @@ test.serial( 2, 3, ]), + context: {} as any, }); - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -109,7 +111,7 @@ test.serial( "returns true when the final partial CC is received and merges its data", (t) => { const { driver } = t.context; - const cc1 = new AssociationCCReport(driver, { + const cc1 = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -121,8 +123,9 @@ test.serial( 2, 3, ]), + context: {} as any, }); - const cc2 = new AssociationCCReport(driver, { + const cc2 = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -134,13 +137,14 @@ test.serial( 5, 6, ]), + context: {} as any, }); - const msg1 = new ApplicationCommandRequest(driver, { + const msg1 = new ApplicationCommandRequest({ command: cc1, }); t.false(driver["assemblePartialCCs"](msg1)); - const msg2 = new ApplicationCommandRequest(driver, { + const msg2 = new ApplicationCommandRequest({ command: cc2, }); t.true(driver["assemblePartialCCs"](msg2)); @@ -154,13 +158,13 @@ test.serial( test.serial("does not crash when receiving a Multi Command CC", (t) => { const { driver } = t.context; - const cc1 = new BasicCCSet(driver, { nodeId: 2, targetValue: 25 }); - const cc2 = new BasicCCSet(driver, { nodeId: 2, targetValue: 50 }); - const cc = new MultiCommandCCCommandEncapsulation(driver, { + const cc1 = new BasicCCSet({ nodeId: 2, targetValue: 25 }); + const cc2 = new BasicCCSet({ nodeId: 2, targetValue: 50 }); + const cc = new MultiCommandCCCommandEncapsulation({ nodeId: 2, encapsulated: [cc1, cc2], }); - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -168,14 +172,16 @@ test.serial("does not crash when receiving a Multi Command CC", (t) => { test.serial("supports nested partial/non-partial CCs", (t) => { const { driver } = t.context; - const cc1 = new BasicCCSet(driver, { nodeId: 2, targetValue: 25 }); - const cc = new SecurityCCCommandEncapsulation(driver, { + const cc1 = new BasicCCSet({ nodeId: 2, targetValue: 25 }); + const cc = new SecurityCCCommandEncapsulation({ nodeId: 2, + ownNodeId: driver.ownNodeId, + securityManager: driver.securityManager!, encapsulated: {} as any, }); cc.encapsulated = undefined as any; - cc["decryptedCCBytes"] = cc1.serialize(); - const msg = new ApplicationCommandRequest(driver, { + cc["decryptedCCBytes"] = cc1.serialize({} as any); + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -183,8 +189,10 @@ test.serial("supports nested partial/non-partial CCs", (t) => { test.serial("supports nested partial/partial CCs (part 1)", (t) => { const { driver } = t.context; - const cc = new SecurityCCCommandEncapsulation(driver, { + const cc = new SecurityCCCommandEncapsulation({ nodeId: 2, + ownNodeId: driver.ownNodeId, + securityManager: driver.securityManager!, encapsulated: {} as any, }); cc.encapsulated = undefined as any; @@ -198,7 +206,7 @@ test.serial("supports nested partial/partial CCs (part 1)", (t) => { 2, 3, ]); - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -206,8 +214,10 @@ test.serial("supports nested partial/partial CCs (part 1)", (t) => { test.serial("supports nested partial/partial CCs (part 2)", (t) => { const { driver } = t.context; - const cc = new SecurityCCCommandEncapsulation(driver, { + const cc = new SecurityCCCommandEncapsulation({ nodeId: 2, + ownNodeId: driver.ownNodeId, + securityManager: driver.securityManager!, encapsulated: {} as any, }); cc.encapsulated = undefined as any; @@ -221,7 +231,7 @@ test.serial("supports nested partial/partial CCs (part 2)", (t) => { 2, 3, ]); - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -231,7 +241,7 @@ test.serial( "returns false when a partial CC throws Deserialization_NotImplemented during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -243,6 +253,7 @@ test.serial( 2, 3, ]), + context: {} as any, }); cc.mergePartialCCs = () => { throw new ZWaveError( @@ -250,7 +261,7 @@ test.serial( ZWaveErrorCodes.Deserialization_NotImplemented, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -261,7 +272,7 @@ test.serial( "returns false when a partial CC throws CC_NotImplemented during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -273,6 +284,7 @@ test.serial( 2, 3, ]), + context: {} as any, }); cc.mergePartialCCs = () => { throw new ZWaveError( @@ -280,7 +292,7 @@ test.serial( ZWaveErrorCodes.CC_NotImplemented, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -291,7 +303,7 @@ test.serial( "returns false when a partial CC throws PacketFormat_InvalidPayload during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -303,6 +315,7 @@ test.serial( 2, 3, ]), + context: {} as any, }); cc.mergePartialCCs = () => { throw new ZWaveError( @@ -310,7 +323,7 @@ test.serial( ZWaveErrorCodes.PacketFormat_InvalidPayload, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -319,7 +332,7 @@ test.serial( test.serial("passes other errors during merging through", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { + const cc = new AssociationCCReport({ nodeId: 2, data: Buffer.from([ CommandClasses.Association, @@ -331,11 +344,12 @@ test.serial("passes other errors during merging through", (t) => { 2, 3, ]), + context: {} as any, }); cc.mergePartialCCs = () => { throw new ZWaveError("invalid", ZWaveErrorCodes.Argument_Invalid); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.throws(() => driver["assemblePartialCCs"](msg), { message: /invalid/ }); diff --git a/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts b/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts index aaee35298ca0..b7d5311af2bd 100644 --- a/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts +++ b/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts @@ -43,9 +43,11 @@ test.afterEach.always(async (t) => { test("should compute the correct net payload sizes", (t) => { const { driver } = t.context; - const testMsg1 = new SendDataRequest(driver, { - command: new SecurityCCCommandEncapsulation(driver, { + const testMsg1 = new SendDataRequest({ + command: new SecurityCCCommandEncapsulation({ nodeId: 2, + ownNodeId: driver.ownNodeId, + securityManager: driver.securityManager!, encapsulated: {} as any, }), transmitOptions: TransmitOptions.DEFAULT, @@ -53,14 +55,16 @@ test("should compute the correct net payload sizes", (t) => { testMsg1.command.encapsulated = undefined as any; t.is(driver.computeNetCCPayloadSize(testMsg1), 26); - const multiChannelCC = new MultiChannelCCCommandEncapsulation(driver, { + const multiChannelCC = new MultiChannelCCCommandEncapsulation({ nodeId: 2, destination: 1, encapsulated: {} as any, }); - const testMsg2 = new SendDataRequest(driver, { - command: new SecurityCCCommandEncapsulation(driver, { + const testMsg2 = new SendDataRequest({ + command: new SecurityCCCommandEncapsulation({ nodeId: 2, + ownNodeId: driver.ownNodeId, + securityManager: driver.securityManager!, encapsulated: multiChannelCC, }), transmitOptions: TransmitOptions.NoRoute, @@ -68,7 +72,7 @@ test("should compute the correct net payload sizes", (t) => { multiChannelCC.encapsulated = undefined as any; t.is(driver.computeNetCCPayloadSize(testMsg2), 54 - 20 - 4); - const testMsg3 = new FirmwareUpdateMetaDataCC(driver, { + const testMsg3 = new FirmwareUpdateMetaDataCC({ nodeId: 2, }); testMsg3.toggleEncapsulationFlag(EncapsulationFlags.Security, true); diff --git a/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts b/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts index dc89fb2d47d9..74beb19e964a 100644 --- a/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts +++ b/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts @@ -42,7 +42,7 @@ integrationTest("update the controller status and wait if TX status is Fail", { customSetup: async (driver, controller, mockNode) => { // Return a TX status of Fail when desired const handleSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof SendDataRequest) { if (!shouldFail) { // Defer to the default behavior @@ -69,10 +69,10 @@ integrationTest("update the controller status and wait if TX status is Fail", { ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); await wait(100); @@ -81,8 +81,8 @@ integrationTest("update the controller status and wait if TX status is Fail", { MockControllerCommunicationState.Idle, ); - const cb = new SendDataRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new SendDataRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: TransmitStatus.Fail, txReport: { txTicks: 0, @@ -91,7 +91,7 @@ integrationTest("update the controller status and wait if TX status is Fail", { ackRSSI: 0, }, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); return true; } else if (msg instanceof SendDataAbort) { @@ -159,7 +159,7 @@ integrationTest( customSetup: async (driver, controller, mockNode) => { // Return a TX status of Fail when desired const handleSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // Soft reset should restore normal operation if (msg instanceof SoftResetRequest) { shouldFail = false; @@ -192,10 +192,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); await wait(100); @@ -204,8 +204,8 @@ integrationTest( MockControllerCommunicationState.Idle, ); - const cb = new SendDataRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new SendDataRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: TransmitStatus.Fail, txReport: { txTicks: 0, @@ -214,7 +214,7 @@ integrationTest( ackRSSI: 0, }, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); return true; } else if (msg instanceof SendDataAbort) { @@ -305,7 +305,7 @@ integrationTestMulti( customSetup: async (driver, controller, mockNodes) => { // Return a TX status of Fail when desired const handleSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof SendDataRequest) { // Commands to node 3 work normally if (msg.getNodeId() === 3) { @@ -333,10 +333,10 @@ integrationTestMulti( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); await wait(100); @@ -345,8 +345,8 @@ integrationTestMulti( MockControllerCommunicationState.Idle, ); - const cb = new SendDataRequestTransmitReport(host, { - callbackId: msg.callbackId, + const cb = new SendDataRequestTransmitReport({ + callbackId: msg.callbackId!, transmitStatus: TransmitStatus.Fail, txReport: { txTicks: 0, @@ -355,7 +355,7 @@ integrationTestMulti( ackRSSI: 0, }, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); return true; } else if (msg instanceof SendDataAbort) { diff --git a/packages/zwave-js/src/lib/test/driver/createCCValuesUsingKnownVersion.test.ts b/packages/zwave-js/src/lib/test/driver/createCCValuesUsingKnownVersion.test.ts index e2c657a71e57..42219793a1f7 100644 --- a/packages/zwave-js/src/lib/test/driver/createCCValuesUsingKnownVersion.test.ts +++ b/packages/zwave-js/src/lib/test/driver/createCCValuesUsingKnownVersion.test.ts @@ -19,8 +19,8 @@ integrationTest("CC values are created using the known CC version", { ), testBody: async (t, driver, node, mockController, mockNode) => { - const batteryReport = new BatteryCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const batteryReport = new BatteryCCReport({ + nodeId: mockController.ownNodeId, isLow: true, }); await mockNode.sendToController( diff --git a/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts b/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts index 610762ea957f..4da3efc61d06 100644 --- a/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/handleNonImplementedCCs.test.ts @@ -22,8 +22,8 @@ integrationTest( 1000, ); - const cc = new CommandClass(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new CommandClass({ + nodeId: mockController.ownNodeId, ccId: CommandClasses["Anti-Theft"], ccCommand: 0x02, // Get payload: Buffer.from([0x00, 0x01]), // Technically invalid diff --git a/packages/zwave-js/src/lib/test/driver/highestSecurityClass.test.ts b/packages/zwave-js/src/lib/test/driver/highestSecurityClass.test.ts index 99c06cc7f5ca..ff1324a68050 100644 --- a/packages/zwave-js/src/lib/test/driver/highestSecurityClass.test.ts +++ b/packages/zwave-js/src/lib/test/driver/highestSecurityClass.test.ts @@ -88,8 +88,9 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => SecurityClass.None; + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => + SecurityClass.None; // Create a security manager for the controller const smCtrlr = new SecurityManager2(); @@ -106,8 +107,10 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => NOT_KNOWN; + controller.securityManagers.securityManager2 = smCtrlr; + controller.encodingContext.getHighestSecurityClass = + controller.parsingContext.getHighestSecurityClass = + () => NOT_KNOWN; }, testBody: async (t, driver, node, mockController, mockNode) => { diff --git a/packages/zwave-js/src/lib/test/driver/ignoreCCVersion0ForKnownSupportedCCs.test.ts b/packages/zwave-js/src/lib/test/driver/ignoreCCVersion0ForKnownSupportedCCs.test.ts index 943f0472c536..d8b586c642f7 100644 --- a/packages/zwave-js/src/lib/test/driver/ignoreCCVersion0ForKnownSupportedCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/ignoreCCVersion0ForKnownSupportedCCs.test.ts @@ -54,8 +54,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -65,19 +65,22 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -99,10 +102,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -125,17 +130,18 @@ integrationTest( instanceof Security2CCCommandsSupportedGet ) { let cc: CommandClass = - new Security2CCCommandsSupportedReport( - self.host, - { - nodeId: controller.host.ownNodeId, - supportedCCs: [ - // The node supports Version CC securely - CommandClasses.Version, - ], - }, - ); - cc = Security2CC.encapsulate(self.host, cc); + new Security2CCCommandsSupportedReport({ + nodeId: controller.ownNodeId, + supportedCCs: [ + // The node supports Version CC securely + CommandClasses.Version, + ], + }); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, + ); return { action: "sendCC", cc }; } }, @@ -152,16 +158,16 @@ integrationTest( && receivedCC.encapsulated.requestedCC === CommandClasses["Security 2"] ) { - let cc: CommandClass = new VersionCCCommandClassReport( - self.host, - { - nodeId: controller.host.ownNodeId, - requestedCC: - receivedCC.encapsulated.requestedCC, - ccVersion: 0, - }, + let cc: CommandClass = new VersionCCCommandClassReport({ + nodeId: controller.ownNodeId, + requestedCC: receivedCC.encapsulated.requestedCC, + ccVersion: 0, + }); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, ); - cc = Security2CC.encapsulate(self.host, cc); return { action: "sendCC", cc }; } }, @@ -194,26 +200,26 @@ integrationTest( networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - mockNode.host.securityManager = sm0Node; + mockNode.securityManagers.securityManager = sm0Node; // Create a security manager for the controller const sm0Ctrlr = new SecurityManager({ - ownNodeId: controller.host.ownNodeId, + ownNodeId: controller.ownNodeId, networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - controller.host.securityManager = sm0Ctrlr; + controller.securityManagers.securityManager = sm0Ctrlr; // Respond to S0 Nonce Get const respondToS0NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SecurityCCNonceGet) { const nonce = sm0Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, 8, ); - const cc = new SecurityCCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SecurityCCNonceReport({ + nodeId: controller.ownNodeId, nonce, }); return { action: "sendCC", cc }; @@ -229,8 +235,8 @@ integrationTest( && receivedCC.encapsulated instanceof SecurityCCCommandsSupportedGet ) { - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -252,18 +258,19 @@ integrationTest( const receiverNonce = nonceReport.payload.nonce; const response: CommandClass = - new SecurityCCCommandsSupportedReport( - self.host, - { - nodeId: controller.host.ownNodeId, - supportedCCs: [ - // The node supports Version CC securely - CommandClasses.Version, - ], - controlledCCs: [], - }, - ); - const cc = SecurityCC.encapsulate(self.host, response); + new SecurityCCCommandsSupportedReport({ + nodeId: controller.ownNodeId, + supportedCCs: [ + // The node supports Version CC securely + CommandClasses.Version, + ], + controlledCCs: [], + }); + const cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + response, + ); cc.nonce = receiverNonce; await self.sendToController( createMockZWaveRequestFrame(cc, { @@ -288,8 +295,8 @@ integrationTest( === CommandClasses.Security ) { await wait(100); - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -311,17 +318,18 @@ integrationTest( const receiverNonce = nonceReport.payload.nonce; const response: CommandClass = - new VersionCCCommandClassReport( - self.host, - { - nodeId: controller.host.ownNodeId, - requestedCC: - receivedCC.encapsulated.requestedCC, - ccVersion: 0, - }, - ); + new VersionCCCommandClassReport({ + nodeId: controller.ownNodeId, + requestedCC: + receivedCC.encapsulated.requestedCC, + ccVersion: 0, + }); - const cc = SecurityCC.encapsulate(self.host, response); + const cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + response, + ); cc.nonce = receiverNonce; await self.sendToController( createMockZWaveRequestFrame(cc, { @@ -342,7 +350,10 @@ integrationTest( if ( receivedCC instanceof SecurityCCCommandEncapsulation ) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs( + [], + {} as any, + ); } return undefined; diff --git a/packages/zwave-js/src/lib/test/driver/multiStageResponseNoTimeout.test.ts b/packages/zwave-js/src/lib/test/driver/multiStageResponseNoTimeout.test.ts index c835f75e5036..abfd5aa06510 100644 --- a/packages/zwave-js/src/lib/test/driver/multiStageResponseNoTimeout.test.ts +++ b/packages/zwave-js/src/lib/test/driver/multiStageResponseNoTimeout.test.ts @@ -37,8 +37,8 @@ integrationTest( async handleCC(controller, self, receivedCC) { if (receivedCC instanceof ConfigurationCCNameGet) { await wait(700); - let cc = new ConfigurationCCNameReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc = new ConfigurationCCNameReport({ + nodeId: controller.ownNodeId, parameter: receivedCC.parameter, name: "Test para", reportsToFollow: 1, @@ -51,8 +51,8 @@ integrationTest( await wait(700); - cc = new ConfigurationCCNameReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new ConfigurationCCNameReport({ + nodeId: controller.ownNodeId, parameter: receivedCC.parameter, name: "meter", reportsToFollow: 0, @@ -100,16 +100,13 @@ integrationTest( const respondToConfigurationNameGet: MockNodeBehavior = { async handleCC(controller, self, receivedCC) { if (receivedCC instanceof ConfigurationCCNameGet) { - const configCC = new ConfigurationCCNameReport( - self.host, - { - nodeId: controller.host.ownNodeId, - parameter: receivedCC.parameter, - name: - "Veeeeeeeeeeeeeeeeeeeeeeeeery loooooooooooooooooong parameter name", - reportsToFollow: 0, - }, - ); + const configCC = new ConfigurationCCNameReport({ + nodeId: controller.ownNodeId, + parameter: receivedCC.parameter, + name: + "Veeeeeeeeeeeeeeeeeeeeeeeeery loooooooooooooooooong parameter name", + reportsToFollow: 0, + }); const serialized = configCC.serialize(); const segment1 = serialized.subarray( 0, @@ -119,25 +116,19 @@ integrationTest( const sessionId = 7; - const tsFS = new TransportServiceCCFirstSegment( - self.host, - { - nodeId: controller.host.ownNodeId, - sessionId, - datagramSize: serialized.length, - partialDatagram: segment1, - }, - ); - const tsSS = new TransportServiceCCSubsequentSegment( - self.host, - { - nodeId: controller.host.ownNodeId, - sessionId, - datagramSize: serialized.length, - datagramOffset: segment1.length, - partialDatagram: segment2, - }, - ); + const tsFS = new TransportServiceCCFirstSegment({ + nodeId: controller.ownNodeId, + sessionId, + datagramSize: serialized.length, + partialDatagram: segment1, + }); + const tsSS = new TransportServiceCCSubsequentSegment({ + nodeId: controller.ownNodeId, + sessionId, + datagramSize: serialized.length, + datagramOffset: segment1.length, + partialDatagram: segment2, + }); await wait(700); await self.sendToController( @@ -185,8 +176,8 @@ integrationTest("GET requests DO time out if there's no matching response", { handleCC(controller, self, receivedCC) { if (receivedCC instanceof ConfigurationCCNameGet) { // This is not the response you're looking for - const cc = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: 1, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/test/driver/nodeAsleepBlockNonceReport.test.ts b/packages/zwave-js/src/lib/test/driver/nodeAsleepBlockNonceReport.test.ts index 8692762b305a..c2ca706a514e 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeAsleepBlockNonceReport.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeAsleepBlockNonceReport.test.ts @@ -31,26 +31,26 @@ integrationTest( networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - mockNode.host.securityManager = sm0Node; + mockNode.securityManagers.securityManager = sm0Node; // Create a security manager for the controller const sm0Ctrlr = new SecurityManager({ - ownNodeId: controller.host.ownNodeId, + ownNodeId: controller.ownNodeId, networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - controller.host.securityManager = sm0Ctrlr; + controller.securityManagers.securityManager = sm0Ctrlr; // Respond to S0 Nonce Get const respondToS0NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SecurityCCNonceGet) { const nonce = sm0Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, 8, ); - const cc = new SecurityCCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SecurityCCNonceReport({ + nodeId: controller.ownNodeId, nonce, }); return { action: "sendCC", cc }; @@ -67,7 +67,10 @@ integrationTest( receivedCC instanceof SecurityCCCommandEncapsulation ) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs( + [], + {} as any, + ); } // This just decodes - we need to call further handlers return undefined; @@ -81,8 +84,8 @@ integrationTest( node.markAsAsleep(); mockNode.autoAckControllerFrames = false; - let nonceRequest = new SecurityCCNonceGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let nonceRequest = new SecurityCCNonceGet({ + nodeId: mockController.ownNodeId, }); await mockNode.sendToController( createMockZWaveRequestFrame(nonceRequest, { @@ -119,8 +122,8 @@ integrationTest( mockNode.autoAckControllerFrames = true; // And subsequent requests must be answered - nonceRequest = new SecurityCCNonceGet(mockNode.host, { - nodeId: mockController.host.ownNodeId, + nonceRequest = new SecurityCCNonceGet({ + nodeId: mockController.ownNodeId, }); await mockNode.sendToController( createMockZWaveRequestFrame(nonceRequest, { diff --git a/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts b/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts index 412b62834e73..994574552cbe 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeAsleepMessageOrder.test.ts @@ -171,12 +171,9 @@ integrationTest( // Node 10 wakes up mockNode10.autoAckControllerFrames = true; - const cc: CommandClass = new WakeUpCCWakeUpNotification( - mockNode10.host, - { - nodeId: mockController.host.ownNodeId, - }, - ); + const cc: CommandClass = new WakeUpCCWakeUpNotification({ + nodeId: mockController.ownNodeId, + }); mockNode10.sendToController(createMockZWaveRequestFrame(cc, { ackRequested: false, })); @@ -306,12 +303,9 @@ integrationTest( // Node 10 wakes up mockNode10.autoAckControllerFrames = true; - const cc: CommandClass = new WakeUpCCWakeUpNotification( - mockNode10.host, - { - nodeId: mockController.host.ownNodeId, - }, - ); + const cc: CommandClass = new WakeUpCCWakeUpNotification({ + nodeId: mockController.ownNodeId, + }); mockNode10.sendToController(createMockZWaveRequestFrame(cc, { ackRequested: false, })); diff --git a/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts b/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts index ccf364258096..4b7ced4e020a 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts @@ -23,7 +23,7 @@ integrationTest( t.is(node2.status, NodeStatus.Awake); - const command1 = new BasicCCSet(driver, { + const command1 = new BasicCCSet({ nodeId: 2, targetValue: 99, }); @@ -31,7 +31,7 @@ integrationTest( maxSendAttempts: 1, }); - const command2 = new BasicCCGet(driver, { + const command2 = new BasicCCGet({ nodeId: 2, }); driver.sendCommand(command2, { diff --git a/packages/zwave-js/src/lib/test/driver/nodeDeadReject.test.ts b/packages/zwave-js/src/lib/test/driver/nodeDeadReject.test.ts index 943270a0dba5..a5d07c6e0b85 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeDeadReject.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeDeadReject.test.ts @@ -20,7 +20,7 @@ integrationTest( t.is(node2.status, NodeStatus.Alive); - const command1 = new BasicCCSet(driver, { + const command1 = new BasicCCSet({ nodeId: 2, targetValue: 99, }); @@ -33,7 +33,7 @@ integrationTest( driver.driverLog.print("basicSetPromise rejected"); }); // Don't throw here, do it below - const command2 = new BasicCCGet(driver, { + const command2 = new BasicCCGet({ nodeId: 2, }); const basicGetPromise = driver.sendCommand(command2, { @@ -88,7 +88,7 @@ integrationTest( t.is(node2.status, NodeStatus.Alive); - const command1 = new BasicCCSet(driver, { + const command1 = new BasicCCSet({ nodeId: 2, targetValue: 99, }); @@ -101,7 +101,7 @@ integrationTest( driver.driverLog.print("basicSetPromise rejected"); }); - const command2 = new BasicCCGet(driver, { + const command2 = new BasicCCGet({ nodeId: 2, }); const basicGetPromise = driver.sendCommand(command2, { diff --git a/packages/zwave-js/src/lib/test/driver/nodeUpdateBeforeCallback.test.ts b/packages/zwave-js/src/lib/test/driver/nodeUpdateBeforeCallback.test.ts index 62db5673455f..a76f267286c0 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeUpdateBeforeCallback.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeUpdateBeforeCallback.test.ts @@ -30,7 +30,7 @@ integrationTest( const respondToBasicGetWithDelayedAck: MockNodeBehavior = { async handleCC(controller, self, receivedCC) { if (receivedCC instanceof BasicCCGet) { - const cc = new BasicCCReport(controller.host, { + const cc = new BasicCCReport({ nodeId: self.id, currentValue: 55, }); diff --git a/packages/zwave-js/src/lib/test/driver/notificationPushNoAGI.test.ts b/packages/zwave-js/src/lib/test/driver/notificationPushNoAGI.test.ts index 6c93deb2f757..9a684da3dd2c 100644 --- a/packages/zwave-js/src/lib/test/driver/notificationPushNoAGI.test.ts +++ b/packages/zwave-js/src/lib/test/driver/notificationPushNoAGI.test.ts @@ -35,8 +35,8 @@ integrationTest( if (receivedCC instanceof NotificationCCGet) { const notificationType = receivedCC.notificationType || 0x06; - const cc = new NotificationCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new NotificationCCReport({ + nodeId: controller.ownNodeId, notificationType, notificationEvent: notificationType === 0x06 ? 0x06 /* Keypad unlock */ diff --git a/packages/zwave-js/src/lib/test/driver/reInterviewAssumeAwake.test.ts b/packages/zwave-js/src/lib/test/driver/reInterviewAssumeAwake.test.ts index 476b895f32a3..1c6cfb43015a 100644 --- a/packages/zwave-js/src/lib/test/driver/reInterviewAssumeAwake.test.ts +++ b/packages/zwave-js/src/lib/test/driver/reInterviewAssumeAwake.test.ts @@ -25,8 +25,8 @@ integrationTest("Assume a node to be awake at the start of a re-interview", { await wait(100); // Send a WakeUpNotification to the node to trigger the interview - const cc = new WakeUpCCWakeUpNotification(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new WakeUpCCWakeUpNotification({ + nodeId: mockController.ownNodeId, }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { @@ -47,8 +47,8 @@ integrationTest("Assume a node to be awake at the start of a re-interview", { waitForWakeup: true, }); - const cc = new WakeUpCCWakeUpNotification(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new WakeUpCCWakeUpNotification({ + nodeId: mockController.ownNodeId, }); await mockNode.sendToController( createMockZWaveRequestFrame(cc, { diff --git a/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts b/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts index 559e5a98a88c..d5c1d155ad4c 100644 --- a/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts +++ b/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts @@ -36,14 +36,14 @@ test.serial( "should not crash if a message is received that cannot be deserialized", async (t) => { const { driver, controller } = t.context; - const req = new ApplicationCommandRequest(driver, { - command: new WakeUpCCIntervalSet(driver, { + const req = new ApplicationCommandRequest({ + command: new WakeUpCCIntervalSet({ nodeId: 1, controllerNodeId: 2, wakeUpInterval: 5, }), }); - controller.serial.emitData(req.serialize()); + controller.serial.emitData(req.serialize(driver)); await controller.expectHostACK(1000); t.pass(); }, diff --git a/packages/zwave-js/src/lib/test/driver/s0AndS2Encapsulation.test.ts b/packages/zwave-js/src/lib/test/driver/s0AndS2Encapsulation.test.ts index fab63ac425c9..2c51783170f9 100644 --- a/packages/zwave-js/src/lib/test/driver/s0AndS2Encapsulation.test.ts +++ b/packages/zwave-js/src/lib/test/driver/s0AndS2Encapsulation.test.ts @@ -49,8 +49,8 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = sm2Node; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = sm2Node; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; const sm0Node = new SecurityManager({ @@ -58,7 +58,7 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - mockNode.host.securityManager = sm0Node; + mockNode.securityManagers.securityManager = sm0Node; // Create a security manager for the controller const smCtrlr = new SecurityManager2(); @@ -75,27 +75,28 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; const sm0Ctrlr = new SecurityManager({ - ownNodeId: controller.host.ownNodeId, + ownNodeId: controller.ownNodeId, networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - controller.host.securityManager = sm0Ctrlr; + controller.securityManagers.securityManager = sm0Ctrlr; // Respond to S0 Nonce Get const respondToS0NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SecurityCCNonceGet) { const nonce = sm0Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, 8, ); - const cc = new SecurityCCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SecurityCCNonceReport({ + nodeId: controller.ownNodeId, nonce, }); return { action: "sendCC", cc }; @@ -109,7 +110,7 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { handleCC(controller, self, receivedCC) { // We don't support sequenced commands here if (receivedCC instanceof SecurityCCCommandEncapsulation) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs([], {} as any); } return undefined; }, @@ -121,10 +122,12 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = sm2Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -146,10 +149,12 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = sm2Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -168,13 +173,17 @@ integrationTest("S0 commands are S0-encapsulated, even when S2 is supported", { receivedCC instanceof Security2CCMessageEncapsulation && receivedCC.encapsulated instanceof SupervisionCCGet ) { - let cc: CommandClass = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc: CommandClass = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.encapsulated.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, }); - cc = Security2CC.encapsulate(self.host, cc); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, + ); return { action: "sendCC", cc }; } }, diff --git a/packages/zwave-js/src/lib/test/driver/s0Encapsulation.test.ts b/packages/zwave-js/src/lib/test/driver/s0Encapsulation.test.ts index 1f5d451ecec9..8d910d3b8988 100644 --- a/packages/zwave-js/src/lib/test/driver/s0Encapsulation.test.ts +++ b/packages/zwave-js/src/lib/test/driver/s0Encapsulation.test.ts @@ -31,26 +31,26 @@ integrationTest("Communication via Security S0 works", { networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - mockNode.host.securityManager = sm0Node; + mockNode.securityManagers.securityManager = sm0Node; // Create a security manager for the controller const sm0Ctrlr = new SecurityManager({ - ownNodeId: controller.host.ownNodeId, + ownNodeId: controller.ownNodeId, networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - controller.host.securityManager = sm0Ctrlr; + controller.securityManagers.securityManager = sm0Ctrlr; // Respond to S0 Nonce Get const respondToS0NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SecurityCCNonceGet) { const nonce = sm0Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, 8, ); - const cc = new SecurityCCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SecurityCCNonceReport({ + nodeId: controller.ownNodeId, nonce, }); return { action: "sendCC", cc }; @@ -67,8 +67,8 @@ integrationTest("Communication via Security S0 works", { && receivedCC.encapsulated instanceof SecurityCCCommandsSupportedGet ) { - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -88,15 +88,16 @@ integrationTest("Communication via Security S0 works", { ); const receiverNonce = nonceReport.payload.nonce; - const response = new SecurityCCCommandsSupportedReport( - self.host, - { - nodeId: controller.host.ownNodeId, - supportedCCs: [CommandClasses.Basic], - controlledCCs: [], - }, + const response = new SecurityCCCommandsSupportedReport({ + nodeId: controller.ownNodeId, + supportedCCs: [CommandClasses.Basic], + controlledCCs: [], + }); + const cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + response, ); - const cc = SecurityCC.encapsulate(self.host, response); cc.nonce = receiverNonce; await self.sendToController( @@ -119,8 +120,8 @@ integrationTest("Communication via Security S0 works", { receivedCC instanceof SecurityCCCommandEncapsulation && receivedCC.encapsulated instanceof BasicCCGet ) { - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -140,11 +141,15 @@ integrationTest("Communication via Security S0 works", { ); const receiverNonce = nonceReport.payload.nonce; - const response = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const response = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: ++queryCount, }); - const cc = SecurityCC.encapsulate(self.host, response); + const cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + response, + ); cc.nonce = receiverNonce; await self.sendToController( @@ -164,7 +169,7 @@ integrationTest("Communication via Security S0 works", { handleCC(controller, self, receivedCC) { // We don't support sequenced commands here if (receivedCC instanceof SecurityCCCommandEncapsulation) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs([], {} as any); } // This just decodes - we need to call further handlers return undefined; diff --git a/packages/zwave-js/src/lib/test/driver/s0EncapsulationTwoNodes.test.ts b/packages/zwave-js/src/lib/test/driver/s0EncapsulationTwoNodes.test.ts index f7bb62b37dec..572a410358ec 100644 --- a/packages/zwave-js/src/lib/test/driver/s0EncapsulationTwoNodes.test.ts +++ b/packages/zwave-js/src/lib/test/driver/s0EncapsulationTwoNodes.test.ts @@ -52,26 +52,26 @@ integrationTest( networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - mockNode.host.securityManager = sm0Node; + mockNode.securityManagers.securityManager = sm0Node; // Create a security manager for the controller const sm0Ctrlr = new SecurityManager({ - ownNodeId: controller.host.ownNodeId, + ownNodeId: controller.ownNodeId, networkKey: driver.options.securityKeys!.S0_Legacy!, nonceTimeout: 100000, }); - controller.host.securityManager = sm0Ctrlr; + controller.securityManagers.securityManager = sm0Ctrlr; // Respond to S0 Nonce Get const respondToS0NonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SecurityCCNonceGet) { const nonce = sm0Node.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, 8, ); - const cc = new SecurityCCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SecurityCCNonceReport({ + nodeId: controller.ownNodeId, nonce, }); return { action: "sendCC", cc }; @@ -88,8 +88,8 @@ integrationTest( && receivedCC.encapsulated instanceof SecurityCCCommandsSupportedGet ) { - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -113,16 +113,14 @@ integrationTest( const receiverNonce = nonceReport.payload.nonce; const response = - new SecurityCCCommandsSupportedReport( - self.host, - { - nodeId: controller.host.ownNodeId, - supportedCCs: [CommandClasses.Basic], - controlledCCs: [], - }, - ); + new SecurityCCCommandsSupportedReport({ + nodeId: controller.ownNodeId, + supportedCCs: [CommandClasses.Basic], + controlledCCs: [], + }); const cc = SecurityCC.encapsulate( - self.host, + self.id, + self.securityManagers.securityManager!, response, ); cc.nonce = receiverNonce; @@ -154,8 +152,8 @@ integrationTest( await wait(750); } - const nonceGet = new SecurityCCNonceGet(self.host, { - nodeId: controller.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: controller.ownNodeId, }); await self.sendToController( createMockZWaveRequestFrame(nonceGet, { @@ -178,12 +176,13 @@ integrationTest( ); const receiverNonce = nonceReport.payload.nonce; - const response = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const response = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: queryCount, }); const cc = SecurityCC.encapsulate( - self.host, + self.id, + self.securityManagers.securityManager!, response, ); cc.nonce = receiverNonce; @@ -207,7 +206,10 @@ integrationTest( if ( receivedCC instanceof SecurityCCCommandEncapsulation ) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs( + [], + {} as any, + ); } // This just decodes - we need to call further handlers return undefined; @@ -229,8 +231,8 @@ integrationTest( await wait(150); // Now send a Nonce Get from node 3, which must be answered immediately - const nonceGet = new SecurityCCNonceGet(mockNode3.host, { - nodeId: mockController.host.ownNodeId, + const nonceGet = new SecurityCCNonceGet({ + nodeId: mockController.ownNodeId, }); await mockNode3.sendToController( createMockZWaveRequestFrame(nonceGet, { diff --git a/packages/zwave-js/src/lib/test/driver/s2Collisions.test.ts b/packages/zwave-js/src/lib/test/driver/s2Collisions.test.ts index 74992534b704..be1ff71d905b 100644 --- a/packages/zwave-js/src/lib/test/driver/s2Collisions.test.ts +++ b/packages/zwave-js/src/lib/test/driver/s2Collisions.test.ts @@ -56,8 +56,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -75,19 +75,22 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -109,10 +112,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -133,16 +138,17 @@ integrationTest( && receivedCC.encapsulated instanceof SupervisionCCGet ) { - let cc: CommandClass = new SupervisionCCReport( - self.host, - { - nodeId: controller.host.ownNodeId, - sessionId: receivedCC.encapsulated.sessionId, - moreUpdatesFollow: false, - status: SupervisionStatus.Success, - }, + let cc: CommandClass = new SupervisionCCReport({ + nodeId: controller.ownNodeId, + sessionId: receivedCC.encapsulated.sessionId, + moreUpdatesFollow: false, + status: SupervisionStatus.Success, + }); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, ); - cc = Security2CC.encapsulate(self.host, cc); return { action: "sendCC", cc }; } }, @@ -162,11 +168,12 @@ integrationTest( // Now create a collision by having both parties send at the same time const nodeToHost = Security2CC.encapsulate( - mockNode.host, - new BinarySwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + new BinarySwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: true, }), + mockNode.id, + mockNode.securityManagers, ); const p1 = mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { @@ -221,8 +228,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -240,19 +247,22 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.securityManagers.securityManager2 = smCtrlr; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -274,10 +284,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -302,11 +314,12 @@ integrationTest( // Now create a collision by having both parties send at the same time const nodeToHost = Security2CC.encapsulate( - mockNode.host, - new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 99, }), + mockNode.id, + mockNode.securityManagers, ); const p1 = mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { @@ -355,8 +368,8 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - mockNode.host.securityManager2 = smNode; - mockNode.host.getHighestSecurityClass = () => + mockNode.securityManagers.securityManager2 = smNode; + mockNode.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Create a security manager for the controller @@ -374,19 +387,21 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => - SecurityClass.S2_Unauthenticated; + controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = + () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get const respondToNonceGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof Security2CCNonceGet) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -408,10 +423,12 @@ integrationTest( === ZWaveErrorCodes.Security2CC_NoSPAN ) { const nonce = smNode.generateNonce( - controller.host.ownNodeId, + controller.ownNodeId, ); - const cc = new Security2CCNonceReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new Security2CCNonceReport({ + nodeId: controller.ownNodeId, + ownNodeId: self.id, + securityManagers: self.securityManagers, SOS: true, MOS: false, receiverEI: nonce, @@ -432,16 +449,17 @@ integrationTest( && receivedCC.encapsulated instanceof SupervisionCCGet ) { - let cc: CommandClass = new SupervisionCCReport( - self.host, - { - nodeId: controller.host.ownNodeId, - sessionId: receivedCC.encapsulated.sessionId, - moreUpdatesFollow: false, - status: SupervisionStatus.Success, - }, + let cc: CommandClass = new SupervisionCCReport({ + nodeId: controller.ownNodeId, + sessionId: receivedCC.encapsulated.sessionId, + moreUpdatesFollow: false, + status: SupervisionStatus.Success, + }); + cc = Security2CC.encapsulate( + cc, + self.id, + self.securityManagers, ); - cc = Security2CC.encapsulate(self.host, cc); return { action: "sendCC", cc }; } }, @@ -465,19 +483,22 @@ integrationTest( const turnOff = node.commandClasses["Binary Switch"].set(false); // Node sends supervised Binary Switch report at the same time - let nodeToHost: CommandClass = new BinarySwitchCCReport( - mockNode.host, - { - nodeId: mockController.host.ownNodeId, - currentValue: true, - }, - ); + let nodeToHost: CommandClass = new BinarySwitchCCReport({ + nodeId: mockController.ownNodeId, + currentValue: true, + }); nodeToHost = SupervisionCC.encapsulate( - mockNode.host, nodeToHost, + driver.getNextSupervisionSessionId( + mockController.ownNodeId, + ), false, ); - nodeToHost = Security2CC.encapsulate(mockNode.host, nodeToHost); + nodeToHost = Security2CC.encapsulate( + nodeToHost, + mockNode.id, + mockNode.securityManagers, + ); const report = mockNode.sendToController( createMockZWaveRequestFrame(nodeToHost, { @@ -503,9 +524,8 @@ integrationTest( // // Now create a collision by having both parties send at the same time // const nodeToHost = Security2CC.encapsulate( - // mockNode.host, - // new BasicCCReport(mockNode.host, { - // nodeId: mockController.host.ownNodeId, + // new BasicCCReport({ + // nodeId: mockController.ownNodeId, // currentValue: 99, // }), // ); diff --git a/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts index 163f4114d347..18d87f88d962 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts @@ -38,7 +38,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -62,24 +62,24 @@ integrationTest( MockControllerCommunicationState.Sending, ); - lastCallbackId = msg.callbackId; + lastCallbackId = msg.callbackId!; // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { // Finish the transmission by sending the callback - const cb = new SendDataRequestTransmitReport(host, { + const cb = new SendDataRequestTransmitReport({ callbackId: lastCallbackId, transmitStatus: TransmitStatus.NoAck, }); setTimeout(() => { - controller.sendToHost(cb.serialize()); + controller.sendMessageToHost(cb); }, 1000); // Put the controller into idle state @@ -95,7 +95,7 @@ integrationTest( mockController.defineBehavior(handleBrokenSendData); const handleSoftReset: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { // Soft reset should restore normal operation if (msg instanceof SoftResetRequest) { shouldTimeOut = false; @@ -160,7 +160,7 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenSendData: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // if (msg instanceof SendDataRequest) { // // Check if this command is legal right now // const state = controller.state.get( @@ -182,10 +182,10 @@ integrationTest( // ); // // Notify the host that the message was sent -// const res = new SendDataResponse(host, { +// const res = new SendDataResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // return true; // } else if (msg instanceof SendDataAbort) { @@ -255,7 +255,7 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenSendData: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // // If the controller is operating normally, defer to the default behavior // if (!shouldTimeOut) return false; @@ -280,10 +280,10 @@ integrationTest( // ); // // Notify the host that the message was sent -// const res = new SendDataResponse(host, { +// const res = new SendDataResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // return true; // } else if (msg instanceof SendDataAbort) { @@ -300,7 +300,7 @@ integrationTest( // mockController.defineBehavior(handleBrokenSendData); // const handleSoftReset: MockControllerBehavior = { -// onHostMessage(host, controller, msg) { +// onHostMessage(controller, msg) { // // Soft reset should restore normal operation // if (msg instanceof SoftResetRequest) { // shouldTimeOut = false; @@ -356,13 +356,13 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenRequestNodeInfo: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // if (msg instanceof RequestNodeInfoRequest) { // // Notify the host that the message was sent -// const res = new RequestNodeInfoResponse(host, { +// const res = new RequestNodeInfoResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // // And never send a callback // return true; diff --git a/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts index 64f25107e7de..f43ae4ca1503 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataFailThrow.test.ts @@ -67,7 +67,7 @@ test.serial( const ACK = Buffer.from([MessageHeaders.ACK]); - const command = new BasicCCSet(driver, { + const command = new BasicCCSet({ nodeId: 2, targetValue: 99, }); @@ -132,7 +132,7 @@ test.serial( const ACK = Buffer.from([MessageHeaders.ACK]); - const command = new BasicCCSet(driver, { + const command = new BasicCCSet({ nodeId: 2, targetValue: 99, }); diff --git a/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts index 99407c84c517..b9de6e4f3b09 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts @@ -54,7 +54,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -79,10 +79,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -99,7 +99,7 @@ integrationTest( mockController.defineBehavior(handleBrokenSendData); const handleSoftReset: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { // Soft reset should restore normal operation if (msg instanceof SoftResetRequest) { shouldTimeOut = false; @@ -158,7 +158,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof SendDataRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -180,10 +180,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -253,7 +253,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -278,10 +278,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -298,7 +298,7 @@ integrationTest( mockController.defineBehavior(handleBrokenSendData); const handleSoftReset: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { // Soft reset should restore normal operation if (msg instanceof SoftResetRequest) { shouldTimeOut = false; @@ -354,13 +354,13 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenRequestNodeInfo: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof RequestNodeInfoRequest) { // Notify the host that the message was sent - const res = new RequestNodeInfoResponse(host, { + const res = new RequestNodeInfoResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); // And never send a callback return true; @@ -409,7 +409,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -434,10 +434,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -520,7 +520,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -545,10 +545,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -628,7 +628,7 @@ integrationTestMulti( customSetup: async (driver, mockController, mockNodes) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -653,10 +653,10 @@ integrationTestMulti( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -740,7 +740,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -765,10 +765,10 @@ integrationTest( ); // Notify the host that the message was sent - const res = new SendDataResponse(host, { + const res = new SendDataResponse({ wasSent: true, }); - await controller.sendToHost(res.serialize()); + await controller.sendMessageToHost(res); return true; } else if (msg instanceof SendDataAbort) { @@ -803,7 +803,7 @@ integrationTest( MockControllerCommunicationState.Idle, ); - const ret = new SerialAPIStartedRequest(mockController.host, { + const ret = new SerialAPIStartedRequest({ wakeUpReason: SerialAPIWakeUpReason.WatchdogReset, watchdogEnabled: true, isListening: true, @@ -811,7 +811,7 @@ integrationTest( supportsLongRange: true, }); setImmediate(async () => { - await mockController.sendToHost(ret.serialize()); + await mockController.sendMessageToHost(ret); }); // And the ping should eventually succeed diff --git a/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts index f4d5b2f25cd3..4d65daafb1dc 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts @@ -37,7 +37,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the response and callback never get sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -55,18 +55,18 @@ integrationTest( ); } - lastCallbackId = msg.callbackId; + lastCallbackId = msg.callbackId!; return true; } else if (msg instanceof SendDataAbort) { // Finish the transmission by sending the callback - const cb = new SendDataRequestTransmitReport(host, { + const cb = new SendDataRequestTransmitReport({ callbackId: lastCallbackId, transmitStatus: TransmitStatus.NoAck, }); setTimeout(() => { - controller.sendToHost(cb.serialize()); + controller.sendMessageToHost(cb); }, 100); // Put the controller into idle state @@ -131,7 +131,7 @@ integrationTest( customSetup: async (driver, mockController, mockNode) => { // This is almost a 1:1 copy of the default behavior, except that the response and callback never get sent const handleBrokenSendData: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { // If the controller is operating normally, defer to the default behavior if (!shouldTimeOut) return false; @@ -149,7 +149,7 @@ integrationTest( ); } - lastCallbackId = msg.callbackId; + lastCallbackId = msg.callbackId!; // Don't send the response or the callback @@ -213,7 +213,7 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenSendData: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // if (msg instanceof SendDataRequest) { // // Check if this command is legal right now // const state = controller.state.get( @@ -235,10 +235,10 @@ integrationTest( // ); // // Notify the host that the message was sent -// const res = new SendDataResponse(host, { +// const res = new SendDataResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // return true; // } else if (msg instanceof SendDataAbort) { @@ -308,7 +308,7 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenSendData: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // // If the controller is operating normally, defer to the default behavior // if (!shouldTimeOut) return false; @@ -333,10 +333,10 @@ integrationTest( // ); // // Notify the host that the message was sent -// const res = new SendDataResponse(host, { +// const res = new SendDataResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // return true; // } else if (msg instanceof SendDataAbort) { @@ -353,7 +353,7 @@ integrationTest( // mockController.defineBehavior(handleBrokenSendData); // const handleSoftReset: MockControllerBehavior = { -// onHostMessage(host, controller, msg) { +// onHostMessage(controller, msg) { // // Soft reset should restore normal operation // if (msg instanceof SoftResetRequest) { // shouldTimeOut = false; @@ -409,13 +409,13 @@ integrationTest( // customSetup: async (driver, mockController, mockNode) => { // // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent // const handleBrokenRequestNodeInfo: MockControllerBehavior = { -// async onHostMessage(host, controller, msg) { +// async onHostMessage(controller, msg) { // if (msg instanceof RequestNodeInfoRequest) { // // Notify the host that the message was sent -// const res = new RequestNodeInfoResponse(host, { +// const res = new RequestNodeInfoResponse({ // wasSent: true, // }); -// await controller.sendToHost(res.serialize()); +// await controller.sendMessageToHost(res); // // And never send a callback // return true; diff --git a/packages/zwave-js/src/lib/test/driver/setValueFailedSupervisionGet.test.ts b/packages/zwave-js/src/lib/test/driver/setValueFailedSupervisionGet.test.ts index dcf1e2a39911..07d31ad25340 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueFailedSupervisionGet.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueFailedSupervisionGet.test.ts @@ -32,8 +32,8 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - const cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Fail, @@ -47,8 +47,8 @@ integrationTest( const respondToBinarySwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof BinarySwitchCCGet) { - const cc = new BinarySwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: controller.ownNodeId, currentValue: false, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/test/driver/setValueNoSupervision.test.ts b/packages/zwave-js/src/lib/test/driver/setValueNoSupervision.test.ts index d58686b1606a..e956097502aa 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueNoSupervision.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueNoSupervision.test.ts @@ -29,8 +29,8 @@ integrationTest("setValue without supervision: expect validation GET", { const respondToBinarySwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof BinarySwitchCCGet) { - const cc = new BinarySwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: controller.ownNodeId, currentValue: false, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/test/driver/setValueSucceedAfterFailure.test.ts b/packages/zwave-js/src/lib/test/driver/setValueSucceedAfterFailure.test.ts index b917f86bc453..216ec6dadf6f 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueSucceedAfterFailure.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueSucceedAfterFailure.test.ts @@ -46,12 +46,12 @@ integrationTest( return { action: "stop" }; } - let cc: CommandClass = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc: CommandClass = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: Math.round(Math.random() * 99), }); - cc = new MultiChannelCCCommandEncapsulation(self.host, { - nodeId: controller.host.ownNodeId, + cc = new MultiChannelCCCommandEncapsulation({ + nodeId: controller.ownNodeId, destination: receivedCC.endpointIndex, endpoint: receivedCC.destination as number, encapsulated: cc, diff --git a/packages/zwave-js/src/lib/test/driver/setValueSuccessfulSupervisionNoGet.test.ts b/packages/zwave-js/src/lib/test/driver/setValueSuccessfulSupervisionNoGet.test.ts index 8feb0bf5a816..b33e29ec0735 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueSuccessfulSupervisionNoGet.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueSuccessfulSupervisionNoGet.test.ts @@ -32,8 +32,8 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - const cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, diff --git a/packages/zwave-js/src/lib/test/driver/setValueSupervision255Get.test.ts b/packages/zwave-js/src/lib/test/driver/setValueSupervision255Get.test.ts index 576d25fe5c99..ff0c5d8d4e75 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueSupervision255Get.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueSupervision255Get.test.ts @@ -32,8 +32,8 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - const cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, @@ -54,16 +54,16 @@ integrationTest( && !!receivedCC.encapsulated.duration ?.toMilliseconds() ) { - const cc1 = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc1 = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: true, status: SupervisionStatus.Working, duration: receivedCC.encapsulated.duration, }); - const cc2 = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc2 = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, @@ -118,8 +118,8 @@ integrationTest( const respondToMultilevelSwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof MultilevelSwitchCCGet) { - const cc = new MultilevelSwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSwitchCCReport({ + nodeId: controller.ownNodeId, targetValue: 88, currentValue: 88, }); diff --git a/packages/zwave-js/src/lib/test/driver/setValueSupervisionSuccessMoreUpdates.test.ts b/packages/zwave-js/src/lib/test/driver/setValueSupervisionSuccessMoreUpdates.test.ts index 6dea3ea9684d..37646ee63fec 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueSupervisionSuccessMoreUpdates.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueSupervisionSuccessMoreUpdates.test.ts @@ -31,8 +31,8 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { async handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - const cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: true, // <-- this is the important part status: SupervisionStatus.Success, diff --git a/packages/zwave-js/src/lib/test/driver/setValueSupervisionWorking.test.ts b/packages/zwave-js/src/lib/test/driver/setValueSupervisionWorking.test.ts index 927a6d01ced7..a5b315070b79 100644 --- a/packages/zwave-js/src/lib/test/driver/setValueSupervisionWorking.test.ts +++ b/packages/zwave-js/src/lib/test/driver/setValueSupervisionWorking.test.ts @@ -43,8 +43,8 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { async handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - let cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + let cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: true, status: SupervisionStatus.Working, @@ -58,8 +58,8 @@ integrationTest( await wait(2000); - cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, + cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Success, diff --git a/packages/zwave-js/src/lib/test/driver/supervisionRepeatedReport.test.ts b/packages/zwave-js/src/lib/test/driver/supervisionRepeatedReport.test.ts index 3e212e4ec7b5..1883f8109adc 100644 --- a/packages/zwave-js/src/lib/test/driver/supervisionRepeatedReport.test.ts +++ b/packages/zwave-js/src/lib/test/driver/supervisionRepeatedReport.test.ts @@ -42,7 +42,7 @@ integrationTest( const respondToSupervisionGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof SupervisionCCGet) { - const cc = new SupervisionCCReport(controller.host, { + const cc = new SupervisionCCReport({ nodeId: self.id, sessionId: receivedCC.sessionId, moreUpdatesFollow: false, diff --git a/packages/zwave-js/src/lib/test/driver/targetValueVersionUnknown.test.ts b/packages/zwave-js/src/lib/test/driver/targetValueVersionUnknown.test.ts index fd296d90b871..eee929d1fc53 100644 --- a/packages/zwave-js/src/lib/test/driver/targetValueVersionUnknown.test.ts +++ b/packages/zwave-js/src/lib/test/driver/targetValueVersionUnknown.test.ts @@ -37,8 +37,8 @@ integrationTest( const respondToBinarySwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof BinarySwitchCCGet) { - const cc = new BinarySwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: controller.ownNodeId, currentValue: true, }); return { action: "sendCC", cc }; @@ -73,8 +73,8 @@ integrationTest( const respondToBinarySwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof BinarySwitchCCGet) { - const cc = new BinarySwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BinarySwitchCCReport({ + nodeId: controller.ownNodeId, currentValue: true, }); return { action: "sendCC", cc }; diff --git a/packages/zwave-js/src/lib/test/driver/unknownValues.test.ts b/packages/zwave-js/src/lib/test/driver/unknownValues.test.ts index 4273ae3c9483..a8d0b7d34dae 100644 --- a/packages/zwave-js/src/lib/test/driver/unknownValues.test.ts +++ b/packages/zwave-js/src/lib/test/driver/unknownValues.test.ts @@ -44,8 +44,8 @@ integrationTest(`Basic Reports with the UNKNOWN state are correctly handled`, { const respondToBasicGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof BasicCCGet) { - const cc = new BasicCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new BasicCCReport({ + nodeId: controller.ownNodeId, currentValue: 0, targetValue: 0, duration: new Duration(0, "seconds"), @@ -66,8 +66,8 @@ integrationTest(`Basic Reports with the UNKNOWN state are correctly handled`, { t.is(node.getValue(currentValueId), 0); // Send an update with UNKNOWN state - const cc = new BasicCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + const cc = new BasicCCReport({ + nodeId: mockController.ownNodeId, currentValue: 254, targetValue: 254, duration: Duration.default(), @@ -113,8 +113,8 @@ integrationTest( t.is(node.getValue(currentValueId), UNKNOWN_STATE); // Send an initial state - let cc = new MultilevelSwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new MultilevelSwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: 0, targetValue: 0, }); @@ -131,8 +131,8 @@ integrationTest( t.is(node.getValue(currentValueId), 0); // Send an update with UNKNOWN state - cc = new MultilevelSwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new MultilevelSwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: 254, targetValue: 254, }); @@ -185,8 +185,8 @@ integrationTest( t.is(node.getValue(currentValueId), NOT_KNOWN); // Send an initial state - let cc = new BinarySwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + let cc = new BinarySwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: false, targetValue: false, duration: new Duration(0, "seconds"), @@ -204,8 +204,8 @@ integrationTest( t.is(node.getValue(currentValueId), false); // Send an update with UNKNOWN state - cc = new BinarySwitchCCReport(mockNode.host, { - nodeId: mockController.host.ownNodeId, + cc = new BinarySwitchCCReport({ + nodeId: mockController.ownNodeId, currentValue: UNKNOWN_STATE, targetValue: UNKNOWN_STATE, duration: Duration.default(), diff --git a/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts b/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts index 340e6b19c18a..6cb55c48f74d 100644 --- a/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts +++ b/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts @@ -19,7 +19,7 @@ integrationTest( async customSetup(driver, mockController, mockNode) { const doNotRespond: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { if (!shouldRespond) { // Soft reset should restore normal operation if (msg instanceof SoftResetRequest) { @@ -50,7 +50,7 @@ integrationTest( { supportCheck: false }, ); - t.is(ids.ownNodeId, mockController.host.ownNodeId); + t.is(ids.ownNodeId, mockController.ownNodeId); }, }, ); @@ -72,7 +72,7 @@ integrationTest( async customSetup(driver, mockController, mockNode) { const doNotRespond: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { if (!shouldRespond) return true; return false; @@ -144,7 +144,7 @@ integrationTest( async customSetup(driver, mockController, mockNode) { const doNotRespond: MockControllerBehavior = { - onHostMessage(host, controller, msg) { + onHostMessage(controller, msg) { if (!shouldRespond) { return true; } diff --git a/packages/zwave-js/src/lib/test/messages.ts b/packages/zwave-js/src/lib/test/messages.ts index 48ed82c9b7e7..d37819af6ce2 100644 --- a/packages/zwave-js/src/lib/test/messages.ts +++ b/packages/zwave-js/src/lib/test/messages.ts @@ -3,7 +3,7 @@ import { MessageType } from "@zwave-js/serial"; const defaultImplementations = { serialize: () => Buffer.from([1, 2, 3]), - getNodeUnsafe: () => undefined, + tryGetNode: () => undefined, getNodeId: () => undefined, toLogEntry: () => ({ tags: [] }), needsCallbackId: () => true, diff --git a/packages/zwave-js/src/lib/test/mocks.ts b/packages/zwave-js/src/lib/test/mocks.ts index b25f320ef62c..40c9e0d56c09 100644 --- a/packages/zwave-js/src/lib/test/mocks.ts +++ b/packages/zwave-js/src/lib/test/mocks.ts @@ -1,23 +1,25 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { getImplementedVersion } from "@zwave-js/cc"; import { ConfigManager } from "@zwave-js/config"; import { type CommandClassInfo, type CommandClasses, type FLiRS, - type IZWaveEndpoint, - type IZWaveNode, - InterviewStage, + type InterviewStage, type MaybeNotKnown, MessagePriority, NOT_KNOWN, - NodeStatus, + type NodeStatus, SecurityClass, ZWaveError, ZWaveErrorCodes, securityClassOrder, } from "@zwave-js/core"; -import type { TestingHost } from "@zwave-js/host"; +import type { + BaseTestEndpoint, + BaseTestNode, + GetNode, + GetValueDB, +} from "@zwave-js/host"; import { type FunctionType, Message, @@ -27,7 +29,6 @@ import { priority, } from "@zwave-js/serial"; import sinon from "sinon"; -import type { Driver } from "../driver/Driver"; import type { ZWaveNode } from "../node/Node"; import * as nodeUtils from "../node/utils"; import { SendDataRequest } from "../serialapi/transport/SendDataMessages"; @@ -171,9 +172,7 @@ export function createEmptyMockDriver() { }, }; ret.sendCommand.callsFake(async (command, options) => { - const msg = new SendDataRequest(ret as unknown as Driver, { - command, - }); + const msg = new SendDataRequest({ command }); const resp = await ret.sendMessage(msg, options); return resp?.command; }); @@ -211,23 +210,27 @@ export interface CreateTestNodeOptions { interviewStage?: InterviewStage; isSecure?: MaybeNotKnown; - numEndpoints?: number; - - supportsCC?: (cc: CommandClasses) => boolean; - controlsCC?: (cc: CommandClasses) => boolean; - isCCSecure?: (cc: CommandClasses) => boolean; - getCCVersion?: (cc: CommandClasses) => number; + commandClasses?: Partial< + Record< + CommandClasses, + Partial + > + >; + endpoints?: Record< + number, + Omit + >; } -export interface TestNode extends IZWaveNode { +export type TestNode = BaseTestNode & { setEndpoint(endpoint: CreateTestEndpointOptions): void; -} +}; export function createTestNode( - host: TestingHost, + host: GetValueDB & GetNode, options: CreateTestNodeOptions, ): TestNode { - const endpointCache = new Map(); + const endpointCache = new Map(); const securityClasses = new Map(); const ret: TestNode = { @@ -235,10 +238,7 @@ export function createTestNode( ...createTestEndpoint(host, { nodeId: options.id, index: 0, - supportsCC: options.supportsCC, - controlsCC: options.controlsCC, - isCCSecure: options.isCCSecure, - getCCVersion: options.getCCVersion, + commandClasses: options.commandClasses, }), isListening: options.isListening ?? true, @@ -249,9 +249,9 @@ export function createTestNode( return !ret.isListening && !ret.isFrequentListening; }, - status: options.status - ?? (options.isListening ? NodeStatus.Alive : NodeStatus.Asleep), - interviewStage: options.interviewStage ?? InterviewStage.Complete, + // status: options.status + // ?? (options.isListening ? NodeStatus.Alive : NodeStatus.Asleep), + // interviewStage: options.interviewStage ?? InterviewStage.Complete, setEndpoint: (endpoint) => { endpointCache.set( @@ -259,37 +259,27 @@ export function createTestNode( createTestEndpoint(host, { nodeId: options.id, index: endpoint.index, - supportsCC: endpoint.supportsCC ?? options.supportsCC, - controlsCC: endpoint.controlsCC ?? options.controlsCC, - isCCSecure: endpoint.isCCSecure ?? options.isCCSecure, - getCCVersion: endpoint.getCCVersion ?? options.getCCVersion, + commandClasses: endpoint.commandClasses, }), ); }, getEndpoint: ((index: number) => { - // When the endpoint count is known, return undefined for non-existent endpoints - if ( - options.numEndpoints != undefined - && index > options.numEndpoints - ) { - return undefined; - } + if (index === 0) return ret; if (!endpointCache.has(index)) { - ret.setEndpoint( - createTestEndpoint(host, { - nodeId: options.id, - index, - supportsCC: options.supportsCC, - controlsCC: options.controlsCC, - isCCSecure: options.isCCSecure, - getCCVersion: options.getCCVersion, - }), - ); + if (!options.endpoints?.[index]) { + return undefined; + } + + ret.setEndpoint({ + nodeId: options.id, + index, + commandClasses: options.endpoints[index].commandClasses, + }); } return endpointCache.get(index); - }) as IZWaveNode["getEndpoint"], + }) as BaseTestNode["getEndpoint"], getEndpointOrThrow(index) { const ep = ret.getEndpoint(index); @@ -302,15 +292,15 @@ export function createTestNode( return ep; }, - getAllEndpoints() { - if (!options.numEndpoints) return [...endpointCache.values()]; - const eps: IZWaveEndpoint[] = []; - for (let i = 0; i <= options.numEndpoints; i++) { - const ep = ret.getEndpoint(i); - if (ep) eps.push(ep); - } - return eps; - }, + // getAllEndpoints() { + // if (!options.numEndpoints) return [...endpointCache.values()]; + // const eps: IZWaveEndpoint[] = []; + // for (let i = 0; i <= options.numEndpoints; i++) { + // const ep = ret.getEndpoint(i); + // if (ep) eps.push(ep); + // } + // return eps; + // }, // These are copied from Node.ts getHighestSecurityClass(): SecurityClass | undefined { @@ -342,10 +332,19 @@ export function createTestNode( endpointCache.set(0, ret); // If the number of endpoints are given, use them as the individual endpoint count - if (options.numEndpoints != undefined) { - nodeUtils.setIndividualEndpointCount(host, ret, options.numEndpoints); - nodeUtils.setAggregatedEndpointCount(host, ret, 0); - nodeUtils.setMultiChannelInterviewComplete(host, ret, true); + if (options.endpoints) { + nodeUtils.setIndividualEndpointCount( + host, + ret.id, + Object.keys(options.endpoints).length, + ); + nodeUtils.setEndpointIndizes( + host, + ret.id, + Object.keys(options.endpoints).map((index) => parseInt(index, 10)), + ); + nodeUtils.setAggregatedEndpointCount(host, ret.id, 0); + nodeUtils.setMultiChannelInterviewComplete(host, ret.id, true); } return ret; @@ -354,42 +353,45 @@ export function createTestNode( export interface CreateTestEndpointOptions { nodeId: number; index: number; - supportsCC?: (cc: CommandClasses) => boolean; - controlsCC?: (cc: CommandClasses) => boolean; - isCCSecure?: (cc: CommandClasses) => boolean; - getCCVersion?: (cc: CommandClasses) => number; + commandClasses?: Partial< + Record< + CommandClasses, + Partial + > + >; } export function createTestEndpoint( - host: TestingHost, + host: GetNode, options: CreateTestEndpointOptions, -): IZWaveEndpoint { - const ret: IZWaveEndpoint = { +): BaseTestEndpoint { + const ret: BaseTestEndpoint = { + virtual: false, nodeId: options.nodeId, index: options.index, - supportsCC: options.supportsCC ?? (() => true), - controlsCC: options.controlsCC ?? (() => false), - isCCSecure: options.isCCSecure ?? (() => false), - getCCVersion: options.getCCVersion - ?? ((cc) => - host.getSafeCCVersion(cc, options.nodeId, options.index)), - virtual: false, - addCC: function( - cc: CommandClasses, - info: Partial, - ): void { - throw new Error("Function not implemented."); + supportsCC: (cc) => { + const ccInfo = options.commandClasses?.[cc]; + if (!ccInfo) return false; + return ccInfo.isSupported ?? true; }, - removeCC: function(cc: CommandClasses): void { - throw new Error("Function not implemented."); + controlsCC: (cc) => { + const ccInfo = options.commandClasses?.[cc]; + if (!ccInfo) return false; + return ccInfo.isControlled ?? false; }, - getCCs: function(): Iterable< - [ccId: CommandClasses, info: CommandClassInfo] - > { - throw new Error("Function not implemented."); + isCCSecure: (cc) => { + const ccInfo = options.commandClasses?.[cc]; + if (!ccInfo) return false; + return ccInfo.secure ?? false; }, - getNodeUnsafe: function(): IZWaveNode | undefined { - return host.nodes.get(options.nodeId); + getCCVersion: (cc) => { + const ccInfo = options.commandClasses?.[cc]; + const defaultVersion = ccInfo?.isSupported + ? getImplementedVersion(cc) + : 0; + return ccInfo?.version + ?? host.getNode(options.nodeId)?.getCCVersion(cc) + ?? defaultVersion; }, }; diff --git a/packages/zwave-js/src/lib/test/node/Node.constructor.test.ts b/packages/zwave-js/src/lib/test/node/Node.constructor.test.ts index 747d9db73a10..033b5d96228d 100644 --- a/packages/zwave-js/src/lib/test/node/Node.constructor.test.ts +++ b/packages/zwave-js/src/lib/test/node/Node.constructor.test.ts @@ -66,9 +66,10 @@ test.serial("stores the given device class", (t) => { const nodeUndef = makeNode(undefined as any); t.is(nodeUndef.deviceClass, undefined); - const devCls = new DeviceClass(driver.configManager, 0x02, 0x01, 0x03); + const devCls = new DeviceClass(0x02, 0x01, 0x03); const nodeWithClass = makeNode(devCls); t.is(nodeWithClass.deviceClass, devCls); + t.is(nodeWithClass.deviceClass?.specific.key, 0x03); nodeUndef.destroy(); nodeWithClass.destroy(); diff --git a/packages/zwave-js/src/lib/test/node/Node.getEndpoint.test.ts b/packages/zwave-js/src/lib/test/node/Node.getEndpoint.test.ts index 2e39e2539d3e..c06730dc4ea4 100644 --- a/packages/zwave-js/src/lib/test/node/Node.getEndpoint.test.ts +++ b/packages/zwave-js/src/lib/test/node/Node.getEndpoint.test.ts @@ -47,7 +47,7 @@ test.beforeEach((t) => { const node = new ZWaveNode( 2, driver, - new DeviceClass(driver.configManager, 0x04, 0x01, 0x01), // Portable Remote Controller + new DeviceClass(0x04, 0x01, 0x01), // Portable Remote Controller ); (driver.controller.nodes as ThrowingMap).set( node.id, diff --git a/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts b/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts index 51e2d32c93c8..a356fdc2d349 100644 --- a/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts +++ b/packages/zwave-js/src/lib/test/node/Node.handleCommand.test.ts @@ -64,17 +64,15 @@ test.serial( } as any; // Handle a command for the root endpoint - const command = new BinarySwitchCCReport( - fakeDriver as unknown as Driver, - { - nodeId: 2, - data: Buffer.from([ - CommandClasses["Binary Switch"], - BinarySwitchCommand.Report, - 0xff, - ]), - }, - ); + const command = new BinarySwitchCCReport({ + nodeId: 2, + data: Buffer.from([ + CommandClasses["Binary Switch"], + BinarySwitchCommand.Report, + 0xff, + ]), + context: {} as any, + }); await node.handleCommand(command); t.true( @@ -119,13 +117,11 @@ test.serial( Buffer.alloc(12, 0xff), ]); - const command = new EntryControlCCNotification( - fakeDriver as unknown as Driver, - { - nodeId: node.id, - data: buf, - }, - ); + const command = new EntryControlCCNotification({ + nodeId: node.id, + data: buf, + context: {} as any, + }); await node.handleCommand(command); diff --git a/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts b/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts index 0dc97d35cc31..f1730da94fb9 100644 --- a/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts +++ b/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts @@ -51,8 +51,8 @@ integrationTest( const respondToMultilevelSwitchGet: MockNodeBehavior = { handleCC(controller, self, receivedCC) { if (receivedCC instanceof MultilevelSwitchCCGet) { - const cc = new MultilevelSwitchCCReport(self.host, { - nodeId: controller.host.ownNodeId, + const cc = new MultilevelSwitchCCReport({ + nodeId: controller.ownNodeId, targetValue: 88, currentValue: 88, }); @@ -64,22 +64,18 @@ integrationTest( }, testBody: async (t, driver, node, mockController, mockNode) => { - const nif = new ApplicationUpdateRequestNodeInfoReceived( - mockController.host, - { - nodeInformation: { - nodeId: node.id, - basicDeviceClass: - mockNode.capabilities.basicDeviceClass, - genericDeviceClass: - mockNode.capabilities.genericDeviceClass, - specificDeviceClass: - mockNode.capabilities.specificDeviceClass, - supportedCCs: [...mockNode.implementedCCs.keys()], - }, + const nif = new ApplicationUpdateRequestNodeInfoReceived({ + nodeInformation: { + nodeId: node.id, + basicDeviceClass: mockNode.capabilities.basicDeviceClass, + genericDeviceClass: + mockNode.capabilities.genericDeviceClass, + specificDeviceClass: + mockNode.capabilities.specificDeviceClass, + supportedCCs: [...mockNode.implementedCCs.keys()], }, - ); - await mockController.sendToHost(nif.serialize()); + }); + await mockController.sendMessageToHost(nif); await wait(100); diff --git a/packages/zwave-js/src/lib/zniffer/CCParsingContext.ts b/packages/zwave-js/src/lib/zniffer/CCParsingContext.ts deleted file mode 100644 index 0e9cd65853bf..000000000000 --- a/packages/zwave-js/src/lib/zniffer/CCParsingContext.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { getImplementedVersion } from "@zwave-js/cc"; -import { DeviceConfig } from "@zwave-js/config"; -import { - type CommandClasses, - type MaybeNotKnown, - SecurityClass, - type SecurityManager, - type SecurityManager2, -} from "@zwave-js/core"; -import { type ZWaveHost } from "@zwave-js/host"; - -export class ZnifferCCParsingContext implements ZWaveHost { - public constructor( - public readonly ownNodeId: number, - public readonly homeId: number, - public readonly securityManager: SecurityManager | undefined, - public readonly securityManager2: SecurityManager2 | undefined, - public readonly securityManagerLR: SecurityManager2 | undefined, - ) {} - - getSafeCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): number { - // We don't know any versions of the node. Try parsing with the highest version we support - return getImplementedVersion(cc); - } - - getSupportedCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): number { - // We don't know any versions of the node. Try parsing with the highest version we support - return getImplementedVersion(cc); - } - - isCCSecure( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): boolean { - // Don't care when parsing - return false; - } - - getHighestSecurityClass(nodeId: number): MaybeNotKnown { - return SecurityClass.S2_AccessControl; - } - - hasSecurityClass( - nodeId: number, - securityClass: SecurityClass, - ): MaybeNotKnown { - // We don't actually know. Attempt parsing with all security classes - return true; - } - - setSecurityClass( - nodeId: number, - securityClass: SecurityClass, - granted: boolean, - ): void { - // Do nothing - } - - getNextCallbackId(): number { - throw new Error("Method not implemented."); - } - - getNextSupervisionSessionId(nodeId: number): number { - throw new Error("Method not implemented."); - } - - getDeviceConfig(nodeId: number): DeviceConfig | undefined { - // Disable strict validation while parsing certain CCs - // Most of this stuff isn't actually needed, only the compat flags... - return new DeviceConfig( - "unknown.json", - false, - "UNKNOWN_MANUFACTURER", - 0x0000, - "UNKNOWN_PRODUCT", - "UNKNOWN_DESCRIPTION", - [], - { - min: "0.0", - max: "255.255", - }, - true, - undefined, - undefined, - undefined, - undefined, - // ...down here: - { - disableStrictEntryControlDataValidation: true, - disableStrictMeasurementValidation: true, - }, - ); - } -} diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 0447d8b11587..f20dd64a0f1e 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -5,15 +5,18 @@ import { Security2CCNonceReport, SecurityCCNonceReport, } from "@zwave-js/cc"; +import { DeviceConfig } from "@zwave-js/config"; import { CommandClasses, type LogConfig, MPDUHeaderType, + type MaybeNotKnown, type RSSI, SPANState, SecurityClass, SecurityManager, SecurityManager2, + type SecurityManagers, type UnknownZWaveChipType, ZWaveError, ZWaveErrorCodes, @@ -24,6 +27,7 @@ import { isLongRangeNodeId, securityClassIsS2, } from "@zwave-js/core"; +import { type CCParsingContext, type HostIDs } from "@zwave-js/host"; import { type ZWaveSerialPortImplementation, type ZnifferDataMessage, @@ -65,7 +69,6 @@ import fs from "node:fs/promises"; import { sdkVersionGte } from "../controller/utils"; import { type ZWaveOptions } from "../driver/ZWaveOptions"; import { ZnifferLogger } from "../log/Zniffer"; -import { ZnifferCCParsingContext } from "./CCParsingContext"; import { type CorruptedFrame, type Frame, @@ -202,12 +205,68 @@ export class Zniffer extends TypedEventEmitter { this._options = options; this._active = false; + + this.parsingContext = { + getHighestSecurityClass( + _nodeId: number, + ): MaybeNotKnown { + return SecurityClass.S2_AccessControl; + }, + + hasSecurityClass( + _nodeId: number, + _securityClass: SecurityClass, + ): MaybeNotKnown { + // We don't actually know. Attempt parsing with all security classes + return true; + }, + + setSecurityClass( + _nodeId: number, + _securityClass: SecurityClass, + _granted: boolean, + ): void { + // Do nothing + }, + + getDeviceConfig(_nodeId: number): DeviceConfig | undefined { + // Disable strict validation while parsing certain CCs + // Most of this stuff isn't actually needed, only the compat flags... + return new DeviceConfig( + "unknown.json", + false, + "UNKNOWN_MANUFACTURER", + 0x0000, + "UNKNOWN_PRODUCT", + "UNKNOWN_DESCRIPTION", + [], + { + min: "0.0", + max: "255.255", + }, + true, + undefined, + undefined, + undefined, + undefined, + // ...down here: + { + disableStrictEntryControlDataValidation: true, + disableStrictMeasurementValidation: true, + }, + ); + }, + }; } private _options: ZnifferOptions; /** The serial port instance */ private serial: ZnifferSerialPortBase | undefined; + private parsingContext: Omit< + CCParsingContext, + keyof HostIDs | "sourceNodeId" | keyof SecurityManagers + >; private _destroyPromise: DeferredPromise | undefined; private get wasDestroyed(): boolean { @@ -506,19 +565,20 @@ supported frequencies: ${ } // TODO: Support parsing multicast S2 frames - - const ctx = new ZnifferCCParsingContext( - destNodeId, - mpdu.homeId, - destSecurityManager, - destSecurityManager2, - destSecurityManagerLR, - ); try { - cc = CommandClass.from(ctx, { + cc = CommandClass.from({ data: mpdu.payload, fromEncapsulation: false, nodeId: mpdu.sourceNodeId, + context: { + homeId: mpdu.homeId, + ownNodeId: destNodeId, + sourceNodeId: mpdu.sourceNodeId, + securityManager: destSecurityManager, + securityManager2: destSecurityManager2, + securityManagerLR: destSecurityManagerLR, + ...this.parsingContext, + }, }); } catch (e: any) { // Ignore diff --git a/test/decodeMessage.ts b/test/decodeMessage.ts index 90942ac69462..df22dbc05bb1 100644 --- a/test/decodeMessage.ts +++ b/test/decodeMessage.ts @@ -5,8 +5,10 @@ import "zwave-js"; import { isCommandClassContainer } from "@zwave-js/cc"; import { ConfigManager } from "@zwave-js/config"; import { + NodeIDType, SPANState, SecurityClass, + type SecurityManager, SecurityManager2, generateAuthKey, generateEncryptionKey, @@ -19,7 +21,7 @@ import { Message } from "@zwave-js/serial"; // The data to decode const data = Buffer.from( - "012900a8000102209f035a0112c1a5ab925f01ee99f1c610bc6c0422f7fd5923f8f1688d1999114000b5d5", + "011100a800000100820343050200a7007f7f25", "hex", ); // The nonce needed to decode it @@ -53,7 +55,7 @@ import { Message } from "@zwave-js/serial"; }); console.log(Message.getMessageLength(data)); - const host: any = { + const host = { getSafeCCVersion: () => 1, getSupportedCCVersion: () => 1, configManager, @@ -81,21 +83,24 @@ import { Message } from "@zwave-js/serial"; get nodes() { return host.controller.nodes; }, + isCCSecure: () => true, + nodeIdType: NodeIDType.Long, + }; + const ctx = { securityManager: { getNonce: () => nonce, deleteNonce() {}, authKey: generateAuthKey(networkKey), encryptionKey: generateEncryptionKey(networkKey), - }, + } as unknown as SecurityManager, securityManager2: sm2, getHighestSecurityClass: () => SecurityClass.S2_AccessControl, hasSecurityClass: () => true, - isCCSecure: () => true, }; - const msg = Message.from(host, { data }); + const msg = Message.from({ data, ctx: ctx as any }); if (isCommandClassContainer(msg)) { - msg.command.mergePartialCCs(host, []); + msg.command.mergePartialCCs([], {} as any); } msg; debugger; diff --git a/yarn.lock b/yarn.lock index 186ad3491e5a..c141033089bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,16 @@ __metadata: languageName: node linkType: hard +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/f3451525379c68a73eb0a1e65247fbf28c0cccd126d93af21c75fceff77773d43c0d4a2d51978fb131aff25b5f2cb41a9fe48cc296e61ae65e679c4f6918b0ab + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0": version: 7.14.5 resolution: "@babel/code-frame@npm:7.14.5" @@ -229,6 +239,187 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/code-frame@npm:7.25.7" + dependencies: + "@babel/highlight": "npm:^7.25.7" + picocolors: "npm:^1.0.0" + checksum: 10/000fb8299fb35b6217d4f6c6580dcc1fa2f6c0f82d0a54b8a029966f633a8b19b490a7a906b56a94e9d8bee91c3bc44c74c44c33fb0abaa588202f6280186291 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.25.7": + version: 7.25.8 + resolution: "@babel/compat-data@npm:7.25.8" + checksum: 10/269fcb0d89e02e36c8a11e0c1b960a6b4204e88f59f20c374d28f8e318f4cd5ded42dfedc4b54162065e6a10f71c0de651f5ed3f9b45d3a4b52240196df85726 + languageName: node + linkType: hard + +"@babel/core@npm:^7.24.7": + version: 7.25.8 + resolution: "@babel/core@npm:7.25.8" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.25.7" + "@babel/generator": "npm:^7.25.7" + "@babel/helper-compilation-targets": "npm:^7.25.7" + "@babel/helper-module-transforms": "npm:^7.25.7" + "@babel/helpers": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.8" + "@babel/template": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/31eb1a8ca1a3cc0026060720eb290e68205d95c5c00fbd831e69ddc0810f5920b8eb2749db1889ac0a0312b6eddbf321d18a996a88858f3b75c9582bef9ec1e4 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/generator@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10/01542829621388077fa8a7464970c1db0f748f1482968dddf5332926afe4003f953cbe08e3bbbb0a335b11eba0126c9a81779bd1c5baed681a9ccec4ae63b217 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + checksum: 10/38044806cab33032391c46861cd0a36adb960525b00bc03f2f3d4380c983bf17971cdabc431e58b93a328ef24bd0271f1dc3a8c1c1ea6cab49d04702961451d8 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-compilation-targets@npm:7.25.7" + dependencies: + "@babel/compat-data": "npm:^7.25.7" + "@babel/helper-validator-option": "npm:^7.25.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10/bbf9be8480da3f9a89e36e9ea2e1c76601014c1074ccada7c2edb1adeb3b62bc402cc4abaf8d16760734b25eceb187a9510ce44f6a7a6f696ccc74f69283625b + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-create-class-features-plugin@npm:7.25.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.25.7" + "@babel/helper-member-expression-to-functions": "npm:^7.25.7" + "@babel/helper-optimise-call-expression": "npm:^7.25.7" + "@babel/helper-replace-supers": "npm:^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/76e3bb727d7541d38acaa9b6ecff88f70e62370396dd22511837b90a556c6815a7efd6fd25b499bf1c8b02cdb18c575781a6aba0c442c38a2129a403b5bf9b1e + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/f953a0ddbcfbaae835033b54fdbf42cc3aea08c554875fccfc02ed4b1e5fe3ee06abf1b7a8419314357841fabc9efdbcbb8afdf07c4f216a73164a45a147562b + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-module-imports@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/94556712c27058ea35a1a39e21a3a9f067cd699405b64333d7d92b2b3d2f24d6f0ffa51aedba0b908e320acb1854e70d296259622e636fb021eeae9a6d996f01 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-module-transforms@npm:7.25.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.25.7" + "@babel/helper-simple-access": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/480309b1272ceaa985de1393f0e4c41aede0d5921ca644cec5aeaf43c8e4192b6dd56a58ef6d7e9acd02a43184ab45d3b241fc8c3a0a00f9dbb30235fd8a1181 + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + checksum: 10/8da0d9f5aae15991678ad1bbe58e52cd62a0ed36871075756d9684c0a7a65988ed81bab53ad6436c39a470d3cd690694dd2b07147817217e3ca87178a129c509 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-plugin-utils@npm:7.25.7" + checksum: 10/e1b0ea5e67b05378d6360e3fc370e99bfb247eed9f68145b5cce541da703424e1887fb6fc60ab2f7f743c72dcbfbed79d3032af43f2c251c229c734dc2572a5b + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-replace-supers@npm:7.25.7" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.25.7" + "@babel/helper-optimise-call-expression": "npm:^7.25.7" + "@babel/traverse": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/87b65c7b278fabcb67458e592082a0b4532d5400acbb51e496ea47763077d0a64dc0531d32bafcb1d51f04d61d4715dadb1fd0301bc8449c26fcfd06913eb45e + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-simple-access@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/42da1c358f2516337a4f2927c77ebb952907543b9f85d7cb1e2b5b5f6d808cdc081ee66a73e2ecdf48c315d9b0c2a81a857d5e1923ea210b8e81aba5e6cd2b53 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/466c81d09981bfb9e10aa6697ecb621389ff92a86187daaca34a969ca990d7327ebe931e87f7d52a200e499542d398469478d83dfaaa244d2f49df4e078490b3 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-string-parser@npm:7.25.7" + checksum: 10/2b8de9fa86c3f3090a349f1ce6e8ee2618a95355cbdafc6f228d82fa4808c84bf3d1d25290c6616d0a18b26b6cfeb6ec2aeebf01404bc8c60051d0094209f0e6 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.14.5": version: 7.14.5 resolution: "@babel/helper-validator-identifier@npm:7.14.5" @@ -236,13 +427,30 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.24.7": +"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.25.7": version: 7.25.7 resolution: "@babel/helper-validator-identifier@npm:7.25.7" checksum: 10/ec6934cc47fc35baaeb968414a372b064f14f7b130cf6489a014c9486b0fd2549b3c6c682cc1fc35080075e8e38d96aeb40342d63d09fc1a62510c8ce25cde1e languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-validator-option@npm:7.25.7" + checksum: 10/3c46cbdd666d176f90a0b7e952a0c6e92184b66633336eca79aca243d1f86085ec339a6e45c3d44efa9e03f1829b470a350ddafa70926af6bbf1ac611284f8d3 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helpers@npm:7.25.7" + dependencies: + "@babel/template": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/2632909f83aa99e8b0da4e10e5ab7fc4f0274e6497bb0f17071e004e037d25e4a595583620261dc21410a526fb32b4f7063c3e15e60ed7890a6f9b8ad52312c5 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.14.5": version: 7.14.5 resolution: "@babel/highlight@npm:7.14.5" @@ -254,6 +462,229 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/highlight@npm:7.25.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10/823be2523d246dbf80aab3cc81c2a36c6111b16ac2949ef06789da54387824c2bfaa88c6627cdeb4ba7151d047a5d6765e49ebd0b478aba09759250111e65e08 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.24.7, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" + dependencies: + "@babel/types": "npm:^7.25.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10/0396eb71e379903cedb43862f84ebb1bec809c41e82b4894d2e6e83b8e8bc636ba6eff45382e615baefdb2399ede76ca82247ecc3a9877ac16eb3140074a3276 + languageName: node + linkType: hard + +"@babel/plugin-syntax-flow@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-syntax-flow@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/ce96b984445c712bb5fdc70a8c7e8a58759db17d1e31386caae5c93b062ab447421831e9527949b0d3d7750ac0a4eacfde00f40ca86392381fec7c5d39455e9c + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/243476a943a84b6b86e99076301e66f48268e8799564053e8feccab90da7944a0b42c91360216dbfb0b2958bbd0ed100d2c7b2db688dab83d19ff2745d4892eb + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-syntax-typescript@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/f1492336230920cc4daa6e7aa3571253fb0c0fd05a1d0a7b5dc0a5b907f31945235ee8bf09c83f7738b89943a2320a61dda95e0db2b6310b07040aeda6be4f44 + languageName: node + linkType: hard + +"@babel/plugin-transform-class-properties@npm:^7.24.7": + version: 7.25.7 + resolution: "@babel/plugin-transform-class-properties@npm:7.25.7" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.25.7" + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/fe1dbbd77275ade96964fec0c85a1f14e2dac0c6565bccddf00680e43c2e906d289dd9d483aff6359420cef2a044b4aaaeb303f64a3a1a005601c018188368e7 + languageName: node + linkType: hard + +"@babel/plugin-transform-flow-strip-types@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-transform-flow-strip-types@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/plugin-syntax-flow": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/af709749aa23deb6aaf8a34818a7de240f80e1877927b55afc566758e64cdf46738385b4f39ad94c9198ae1215f011a76ad3a779ac4a48092d349f53ef942ce0 + languageName: node + linkType: hard + +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.25.7" + dependencies: + "@babel/helper-module-transforms": "npm:^7.25.7" + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/helper-simple-access": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/4b3d038b55bfe5553e9eea360cc1b3dd689068256a9bce1939061ab1dfa194fea0b7b54f10c53b0af0be44508fd0037022c32709a6d96ac1277fb9c7de0f510c + languageName: node + linkType: hard + +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": + version: 7.25.8 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.8" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/d742fedc1abf404d7f40065cdff9afc521236607f0d06c48d1e471f43d3a7471010d1651ba4758d80c73347a39dc278d86c43a9c814382ded4e9c7c519ace021 + languageName: node + linkType: hard + +"@babel/plugin-transform-optional-chaining@npm:^7.24.7": + version: 7.25.8 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.8" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/ffb5d81e6dbb28907d5346c8e12a1ed1ea0e30170fbe609d48d0466cdbc9d11b5774c8781682693f7cf7bd39da6111980e54813af96c6b3086dc769369c67d28 + languageName: node + linkType: hard + +"@babel/plugin-transform-private-methods@npm:^7.24.7": + version: 7.25.7 + resolution: "@babel/plugin-transform-private-methods@npm:7.25.7" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.25.7" + "@babel/helper-plugin-utils": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/79506a74334dc77f6c53f44109f0a3fcf6c50410faa5dd5e5d17ac4b73194098de509f5515a7aed3724a4bfa5dd246517e22a1dff4c20fc052df7a189bf2160d + languageName: node + linkType: hard + +"@babel/plugin-transform-typescript@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/plugin-transform-typescript@npm:7.25.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.25.7" + "@babel/helper-create-class-features-plugin": "npm:^7.25.7" + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7" + "@babel/plugin-syntax-typescript": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/2648da981cd71c2100a4ea496684f2c0b939fc77eb4bb9cc8f113d433eab17d4930d2e5ed8d280606bcedef58df99002a64dc92579c6cc7f6c6ee71ceaa77418 + languageName: node + linkType: hard + +"@babel/preset-flow@npm:^7.24.7": + version: 7.25.7 + resolution: "@babel/preset-flow@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/helper-validator-option": "npm:^7.25.7" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/b96e1a4bcd7a5bf868def9ea9e1097f0b96d8d3e5458668268a47906f68fd43617601274511dcf3cb47f519ef9d6ed88788282610ad926b864f9c8149c4a04b8 + languageName: node + linkType: hard + +"@babel/preset-typescript@npm:^7.24.7": + version: 7.25.7 + resolution: "@babel/preset-typescript@npm:7.25.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.25.7" + "@babel/helper-validator-option": "npm:^7.25.7" + "@babel/plugin-syntax-jsx": "npm:^7.25.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.25.7" + "@babel/plugin-transform-typescript": "npm:^7.25.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/cf6501971f696800096f3b3aef4f7e2c774210b4204bb3a076b2f253970d6649c28003de3afc620b7c7ad67fb346083819c89bae2c644f59995ddb64d6003541 + languageName: node + linkType: hard + +"@babel/register@npm:^7.24.6": + version: 7.25.7 + resolution: "@babel/register@npm:7.25.7" + dependencies: + clone-deep: "npm:^4.0.1" + find-cache-dir: "npm:^2.0.0" + make-dir: "npm:^2.1.0" + pirates: "npm:^4.0.6" + source-map-support: "npm:^0.5.16" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/bb375399aea6d13b92eb4e40befb5ca8ec226d753aa3ff8d9be2c284805a133e3afe7128a7b103af7abb164868c273bd80ea9a9e55a9ec38ef17df4b977f1cd7 + languageName: node + linkType: hard + +"@babel/template@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/template@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10/49e1e88d2eac17d31ae28d6cf13d6d29c1f49384c4f056a6751c065d6565c351e62c01ce6b11fef5edb5f3a77c87e114ea7326ca384fa618b4834e10cf9b20f3 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/traverse@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/generator": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/template": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10/5b2d332fcd6bc78e6500c997e79f7e2a54dfb357e06f0908cb7f0cdd9bb54e7fd3c5673f45993849d433d01ea6076a6d04b825958f0cfa01288ad55ffa5c286f + languageName: node + linkType: hard + +"@babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10/973108dbb189916bb87360f2beff43ae97f1b08f1c071bc6499d363cce48b3c71674bf3b59dfd617f8c5062d1c76dc2a64232bc07b6ccef831fd0c06162d44d9 + languageName: node + linkType: hard + "@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": version: 1.6.0 resolution: "@colors/colors@npm:1.6.0" @@ -900,6 +1331,48 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/81587b3c4dd8e6c60252122937cea0c637486311f4ed208b52b62aae2e7a87598f63ec330e6cd0984af494bfb16d3f0d60d3b21d7e5b4aedd2602ff3fe9d32e2 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10/dced32160a44b49d531b80a4a2159dceab6b3ddf0c8e95a0deae4b0e894b172defa63d5ac52a19c2068e1fe7d31ea4ba931fbeec103233ecb4208953967120fc + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -1680,6 +2153,16 @@ __metadata: languageName: node linkType: hard +"@types/jscodeshift@npm:^0.12.0": + version: 0.12.0 + resolution: "@types/jscodeshift@npm:0.12.0" + dependencies: + ast-types: "npm:^0.14.1" + recast: "npm:^0.20.3" + checksum: 10/1e0fd4a5ee2863db792ad5666fa98ee61d3d5a4d5f24a9b8d11cf6ebae25bff55a3e5e27a59fef3aed37637458e7ab52b30181640be39cddbe9956f4a47fd1ab + languageName: node + linkType: hard + "@types/json-logic-js@npm:^2.0.7": version: 2.0.7 resolution: "@types/json-logic-js@npm:2.0.7" @@ -2162,6 +2645,7 @@ __metadata: "@dprint/typescript": "npm:^0.93.0" "@types/fs-extra": "npm:^11.0.4" "@types/globrex": "npm:^0.1.4" + "@types/jscodeshift": "npm:^0.12.0" "@types/node": "npm:^18.19.55" "@types/yargs": "npm:^17.0.33" "@zwave-js/core": "workspace:*" @@ -2176,6 +2660,7 @@ __metadata: execa: "npm:^5.1.1" fs-extra: "npm:^11.2.0" globrex: "npm:^0.1.2" + jscodeshift: "npm:^17.0.0" json5: "npm:^2.2.3" piscina: "npm:^4.7.0" reflect-metadata: "npm:^0.2.2" @@ -2798,6 +3283,24 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:0.14.2, ast-types@npm:^0.14.1": + version: 0.14.2 + resolution: "ast-types@npm:0.14.2" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10/7c74b3090c90aa600b49a7a8cecc99e329f190600bcaa75ad087472a1a5a7ef23795a17ea00a74c2a8e822b336cd4f874e2e1b815a9877b4dba5e401566b0433 + languageName: node + linkType: hard + +"ast-types@npm:^0.16.1": + version: 0.16.1 + resolution: "ast-types@npm:0.16.1" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10/f569b475eb1c8cb93888cb6e7b7e36dc43fa19a77e4eb132cbff6e3eb1598ca60f850db6e60b070e5a0ee8c1559fca921dac0916e576f2f104e198793b0bdd8d + languageName: node + linkType: hard + "async-sema@npm:^3.1.1": version: 3.1.1 resolution: "async-sema@npm:3.1.1" @@ -2982,6 +3485,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.24.0": + version: 4.24.0 + resolution: "browserslist@npm:4.24.0" + dependencies: + caniuse-lite: "npm:^1.0.30001663" + electron-to-chromium: "npm:^1.5.28" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 10/26c1b8ba257a0b51b102080ba9d42945af2abaa8c4cf6da21cd47b3f123fc1e81640203b293214356c2c17d9d265bb3a5ed428b6d302f383576dd6ce8fd5036c + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.1 resolution: "buffer-from@npm:1.1.1" @@ -3116,6 +3633,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001663": + version: 1.0.30001668 + resolution: "caniuse-lite@npm:1.0.30001668" + checksum: 10/4a14acbc999a855e6a91a3ae4ca670f202ceabb4b0e75f8eaef153fafe33ae5ea0de7ac99c078d6b724c8f60b14b1ea24d7c544398e5fd077c418e3f029559ff + languageName: node + linkType: hard + "cbor@npm:^9.0.1": version: 9.0.2 resolution: "cbor@npm:9.0.2" @@ -3328,6 +3852,17 @@ __metadata: languageName: node linkType: hard +"clone-deep@npm:^4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: "npm:^2.0.4" + kind-of: "npm:^6.0.2" + shallow-clone: "npm:^3.0.0" + checksum: 10/770f912fe4e6f21873c8e8fbb1e99134db3b93da32df271d00589ea4a29dbe83a9808a322c93f3bcaf8584b8b4fa6fc269fc8032efbaa6728e0c9886c74467d2 + languageName: node + linkType: hard + "clone@npm:^1.0.2": version: 1.0.4 resolution: "clone@npm:1.0.4" @@ -3557,6 +4092,13 @@ __metadata: languageName: node linkType: hard +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 10/4620bc4936a4ef12ce7dfcd272bb23a99f2ad68889a4e4ad766c9f8ad21af982511934d6f7050d4a8bde90011b1c15d56e61a1b4576d9913efbf697a20172d6c + languageName: node + linkType: hard + "compare-func@npm:^2.0.0": version: 2.0.0 resolution: "compare-func@npm:2.0.0" @@ -3636,6 +4178,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10/c987be3ec061348cdb3c2bfb924bec86dea1eacad10550a85ca23edb0fe3556c3a61c7399114f3331ccb3499d7fd0285ab24566e5745929412983494c3926e15 + languageName: node + linkType: hard + "convert-to-spaces@npm:^2.0.1": version: 2.0.1 resolution: "convert-to-spaces@npm:2.0.1" @@ -4083,6 +4632,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.28": + version: 1.5.36 + resolution: "electron-to-chromium@npm:1.5.36" + checksum: 10/659f637b7384714d5a732de0e5baca007fa1ae741faa4a0f9eb576d65a6a6d30c553caae27df5df7307c65484c0fbcd2ac453df27848d04f7dd27b81dea072a2 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.5.4": version: 1.5.24 resolution: "electron-to-chromium@npm:1.5.24" @@ -4510,7 +5066,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0, esprima@npm:^4.0.1": +"esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -4826,6 +5382,17 @@ __metadata: languageName: node linkType: hard +"find-cache-dir@npm:^2.0.0": + version: 2.1.0 + resolution: "find-cache-dir@npm:2.1.0" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^2.0.0" + pkg-dir: "npm:^3.0.0" + checksum: 10/60ad475a6da9f257df4e81900f78986ab367d4f65d33cf802c5b91e969c28a8762f098693d7a571b6e4dd4c15166c2da32ae2d18b6766a18e2071079448fdce4 + languageName: node + linkType: hard + "find-node-modules@npm:^2.1.2": version: 2.1.2 resolution: "find-node-modules@npm:2.1.2" @@ -4850,6 +5417,15 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^3.0.0": + version: 3.0.0 + resolution: "find-up@npm:3.0.0" + dependencies: + locate-path: "npm:^3.0.0" + checksum: 10/38eba3fe7a66e4bc7f0f5a1366dc25508b7cfc349f852640e3678d26ad9a6d7e2c43eff0a472287de4a9753ef58f066a0ea892a256fa3636ad51b3fe1e17fae9 + languageName: node + linkType: hard + "find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -4910,6 +5486,13 @@ __metadata: languageName: node linkType: hard +"flow-parser@npm:0.*": + version: 0.248.1 + resolution: "flow-parser@npm:0.248.1" + checksum: 10/bc54edb5fb842ee467b67459bba7bbc54e66eef81ed4ba87e6d28f10306fd32d925b80a10522200c134a51bdd512723d88887fb2275b89b54b89281cd38751cd + languageName: node + linkType: hard + "fn.name@npm:1.x.x": version: 1.1.0 resolution: "fn.name@npm:1.1.0" @@ -5064,6 +5647,13 @@ __metadata: languageName: node linkType: hard +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10/17d8333460204fbf1f9160d067e1e77f908a5447febb49424b8ab043026049835c9ef3974445c57dbd39161f4d2b04356d7de12b2eecaa27a7a7ea7d871cbedd + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -5218,6 +5808,13 @@ __metadata: languageName: node linkType: hard +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 + languageName: node + linkType: hard + "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -5867,6 +6464,15 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10/2a401140cfd86cabe25214956ae2cfee6fbd8186809555cd0e84574f88de7b17abacb2e477a6a658fa54c6083ecbda1e6ae404c7720244cd198903848fca70ca + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -5946,6 +6552,13 @@ __metadata: languageName: node linkType: hard +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10/db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 + languageName: node + linkType: hard + "jiti@npm:^1.19.1": version: 1.21.0 resolution: "jiti@npm:1.21.0" @@ -6006,6 +6619,39 @@ __metadata: languageName: node linkType: hard +"jscodeshift@npm:^17.0.0": + version: 17.0.0 + resolution: "jscodeshift@npm:17.0.0" + dependencies: + "@babel/core": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/plugin-transform-class-properties": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/preset-flow": "npm:^7.24.7" + "@babel/preset-typescript": "npm:^7.24.7" + "@babel/register": "npm:^7.24.6" + flow-parser: "npm:0.*" + graceful-fs: "npm:^4.2.4" + micromatch: "npm:^4.0.7" + neo-async: "npm:^2.5.0" + picocolors: "npm:^1.0.1" + recast: "npm:^0.23.9" + temp: "npm:^0.9.4" + write-file-atomic: "npm:^5.0.1" + peerDependencies: + "@babel/preset-env": ^7.1.6 + peerDependenciesMeta: + "@babel/preset-env": + optional: true + bin: + jscodeshift: bin/jscodeshift.js + checksum: 10/d47000c33775336332e1ee431354881ce25a42354a82c712e109f598eac90be747d412c85428e1fa0302136823e44e4f42487746d62752120cd6879665bcef0a + languageName: node + linkType: hard + "jsesc@npm:^3.0.2": version: 3.0.2 resolution: "jsesc@npm:3.0.2" @@ -6242,6 +6888,16 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-path@npm:3.0.0" + dependencies: + p-locate: "npm:^3.0.0" + path-exists: "npm:^3.0.0" + checksum: 10/53db3996672f21f8b0bf2a2c645ae2c13ffdae1eeecfcd399a583bce8516c0b88dcb4222ca6efbbbeb6949df7e46860895be2c02e8d3219abd373ace3bfb4e11 + languageName: node + linkType: hard + "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -6431,6 +7087,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10/951d2673dcc64a7fb888bf3d13bc2fdf923faca97d89cdb405ba3dfff77e2b26e5798d405e78fcd7094c9e7b8b4dab2ddc5a4f8a11928af24a207b7c738ca3f8 + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -6440,6 +7105,16 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": + version: 2.1.0 + resolution: "make-dir@npm:2.1.0" + dependencies: + pify: "npm:^4.0.1" + semver: "npm:^5.6.0" + checksum: 10/043548886bfaf1820323c6a2997e6d2fa51ccc2586ac14e6f14634f7458b4db2daf15f8c310e2a0abd3e0cddc64df1890d8fc7263033602c47bb12cbfcf86aab + languageName: node + linkType: hard + "make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -6603,7 +7278,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:~4.0.8": +"micromatch@npm:^4.0.7, micromatch@npm:~4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -6809,6 +7484,17 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^0.5.1": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10/0c91b721bb12c3f9af4b77ebf73604baf350e64d80df91754dc509491ae93bf238581e59c7188360cec7cb62fc4100959245a42cfe01834efedc5e9d068376c2 + languageName: node + linkType: hard + "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -6894,6 +7580,13 @@ __metadata: languageName: node linkType: hard +"neo-async@npm:^2.5.0": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 + languageName: node + linkType: hard + "nested-error-stacks@npm:^2.1.1": version: 2.1.1 resolution: "nested-error-stacks@npm:2.1.1" @@ -7210,7 +7903,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.2.0": +"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" dependencies: @@ -7237,6 +7930,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^3.0.0": + version: 3.0.0 + resolution: "p-locate@npm:3.0.0" + dependencies: + p-limit: "npm:^2.0.0" + checksum: 10/83991734a9854a05fe9dbb29f707ea8a0599391f52daac32b86f08e21415e857ffa60f0e120bfe7ce0cc4faf9274a50239c7895fc0d0579d08411e513b83a4ae + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -7402,6 +8104,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^3.0.0": + version: 3.0.0 + resolution: "path-exists@npm:3.0.0" + checksum: 10/96e92643aa34b4b28d0de1cd2eba52a1c5313a90c6542d03f62750d82480e20bfa62bc865d5cfc6165f5fcd5aeb0851043c40a39be5989646f223300021bae0a + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -7484,7 +8193,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": version: 1.1.0 resolution: "picocolors@npm:1.1.0" checksum: 10/a2ad60d94d185c30f2a140b19c512547713fb89b920d32cc6cf658fa786d63a37ba7b8451872c3d9fc34883971fb6e5878e07a20b60506e0bb2554dce9169ccb @@ -7528,6 +8237,20 @@ __metadata: languageName: node linkType: hard +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10/8b97cbf9dc6d4c1320cc238a2db0fc67547f9dc77011729ff353faf34f1936ea1a4d7f3c63b2f4980b253be77bcc72ea1e9e76ee3fd53cce2aafb6a8854d07ec + languageName: node + linkType: hard + +"pirates@npm:^4.0.6": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 10/d02dda76f4fec1cbdf395c36c11cf26f76a644f9f9a1bfa84d3167d0d3154d5289aacc72677aa20d599bb4a6937a471de1b65c995e2aea2d8687cbcd7e43ea5f + languageName: node + linkType: hard + "piscina@npm:^4.7.0": version: 4.7.0 resolution: "piscina@npm:4.7.0" @@ -7540,6 +8263,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "pkg-dir@npm:3.0.0" + dependencies: + find-up: "npm:^3.0.0" + checksum: 10/70c9476ffefc77552cc6b1880176b71ad70bfac4f367604b2b04efd19337309a4eec985e94823271c7c0e83946fa5aeb18cd360d15d10a5d7533e19344bfa808 + languageName: node + linkType: hard + "plur@npm:^5.1.0": version: 5.1.0 resolution: "plur@npm:5.1.0" @@ -7738,6 +8470,31 @@ __metadata: languageName: node linkType: hard +"recast@npm:^0.20.3": + version: 0.20.5 + resolution: "recast@npm:0.20.5" + dependencies: + ast-types: "npm:0.14.2" + esprima: "npm:~4.0.0" + source-map: "npm:~0.6.1" + tslib: "npm:^2.0.1" + checksum: 10/7b270187e12f06ba0f5695590158005170a49a5996ab5d30ec4af2a2b1db8b0f74b1449b7eb6984f6d381438448e05cb46dcbf9b647fc49c6fc5139b2e40fca0 + languageName: node + linkType: hard + +"recast@npm:^0.23.9": + version: 0.23.9 + resolution: "recast@npm:0.23.9" + dependencies: + ast-types: "npm:^0.16.1" + esprima: "npm:~4.0.0" + source-map: "npm:~0.6.1" + tiny-invariant: "npm:^1.3.3" + tslib: "npm:^2.0.1" + checksum: 10/d60584be179d81a82fbe58b5bbe009aa42831ee114a21a3e3a22bda91334e0b8a1a4610dca8ecb7f9ea1426da4febc08134d3003085ad6ecee478d1808eb8796 + languageName: node + linkType: hard + "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -8040,6 +8797,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:~2.6.2": + version: 2.6.3 + resolution: "rimraf@npm:2.6.3" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: ./bin.js + checksum: 10/756419f2fa99aa119c46a9fc03e09d84ecf5421a80a72d1944c5088c9e4671e77128527a900a313ed9d3fdbdd37e2ae05486cd7e9116d5812d8c31f2399d7c86 + languageName: node + linkType: hard + "run-async@npm:^2.2.0, run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -8134,7 +8902,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0": +"semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10/fca14418a174d4b4ef1fecb32c5941e3412d52a4d3d85165924ce3a47fbc7073372c26faf7484ceb4bbc2bde25880c6b97e492473dc7e9708fdfb1c6a02d546e + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" bin: @@ -8245,6 +9022,15 @@ __metadata: languageName: node linkType: hard +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10/e066bd540cfec5e1b0f78134853e0d892d1c8945fb9a926a579946052e7cb0c70ca4fc34f875a8083aa7910d751805d36ae64af250a6de6f3d28f9fa7be6c21b + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -8374,7 +9160,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.21": +"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -8733,6 +9519,16 @@ __metadata: languageName: node linkType: hard +"temp@npm:^0.9.4": + version: 0.9.4 + resolution: "temp@npm:0.9.4" + dependencies: + mkdirp: "npm:^0.5.1" + rimraf: "npm:~2.6.2" + checksum: 10/38d40564656c6e8e3caee41c592b3cc076d257ab4746ae4a6a179c44eb4a6d3e8a19a08c7716c8e88756bb898d6e56dd0a9e0408249bbcb3c990a178c34d0571 + languageName: node + linkType: hard + "text-extensions@npm:^2.0.0": version: 2.4.0 resolution: "text-extensions@npm:2.4.0" @@ -8796,6 +9592,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 10/5e185c8cc2266967984ce3b352a4e57cb89dad5a8abb0dea21468a6ecaa67cd5bb47a3b7a85d08041008644af4f667fb8b6575ba38ba5fb00b3b5068306e59fe + languageName: node + linkType: hard + "tinyexec@npm:^0.3.0": version: 0.3.0 resolution: "tinyexec@npm:0.3.0" @@ -8822,6 +9625,13 @@ __metadata: languageName: node linkType: hard +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10/be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -8919,6 +9729,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.0.1, tslib@npm:^2.6.3": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 + languageName: node + linkType: hard + "tslib@npm:^2.1.0": version: 2.4.0 resolution: "tslib@npm:2.4.0" @@ -8933,13 +9750,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.6.3": - version: 2.7.0 - resolution: "tslib@npm:2.7.0" - checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -9448,6 +10258,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10/9af0a4329c3c6b779ac4736c69fae4190ac03029fa27c1aef4e6bcc92119b73dea6fe5db5fe881fb0ce2a0e9539a42cdf60c7c21eda04d1a0b8c082e38509efb + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0"