From 5e8d03b9a053c61e465d6a34317c494a9160d082 Mon Sep 17 00:00:00 2001 From: Reuben Bijl Date: Sun, 6 Feb 2022 21:51:04 +1300 Subject: [PATCH 1/8] Update zwa003.json added fingerprint for AU/NZ model of Aeotec ZWA003 --- packages/config/config/devices/0x0371/zwa003.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/config/config/devices/0x0371/zwa003.json b/packages/config/config/devices/0x0371/zwa003.json index 7180b6d1d1ab..3fd33e96620c 100644 --- a/packages/config/config/devices/0x0371/zwa003.json +++ b/packages/config/config/devices/0x0371/zwa003.json @@ -14,6 +14,10 @@ "productId": "0x0003", "zwaveAllianceId": 2817 }, + { + "productType": "0x0202", + "productId": "0x0003" + }, { "productType": "0x1c02", "productId": "0x0003", From 0f9890e0a52f2e5c4d4b946dc76058de23320840 Mon Sep 17 00:00:00 2001 From: Reuben Bijl Date: Fri, 25 Feb 2022 15:43:50 +1300 Subject: [PATCH 2/8] Working towards adding Multilevel Switch Set as described here - https://github.com/zwave-js/node-zwave-js/issues/4064 --- .../lib/commandclass/MultilevelSwitchCC.ts | 62 ++++++++++++++++--- packages/zwave-js/src/lib/node/Node.ts | 29 +++++++++ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts index 4d5d191f75e6..2526a5672ae5 100644 --- a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts +++ b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts @@ -109,6 +109,14 @@ function getSuperviseStartStopLevelChangeValueId(): ValueID { }; } +export function getCompatEventValueId(endpoint?: number): ValueID { + return { + commandClass: CommandClasses["Multilevel Switch"], + endpoint, + property: "event", + }; +} + @API(CommandClasses["Multilevel Switch"]) export class MultilevelSwitchCCAPI extends CCAPI { public supportsCommand(cmd: MultilevelSwitchCommand): Maybe { @@ -497,6 +505,17 @@ export class MultilevelSwitchCC extends CommandClass { await this.refreshValues(); + // create compat event value if necessary + if (node.deviceConfig?.compat?.treatBasicSetAsEvent) { + const valueId = getCompatEventValueId(this.endpointIndex); + if (!node.valueDB.hasMetadata(valueId)) { + node.valueDB.setMetadata(valueId, { + ...ValueMetadata.ReadOnlyUInt8, + label: "Event value", + }); + } + } + // Remember that the interview is complete this.interviewComplete = true; } @@ -581,24 +600,49 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { ) { super(driver, options); if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + validatePayload(this.payload.length >= 1); + this.currentValue = parseMaybeNumber( + this.payload[0], + driver.options.preserveUnknownValues, ); + + if (this.version >= 2 && this.payload.length >= 2) { + this.targetValue = parseNumber(this.payload[0]); + this.duration = Duration.parseReport(this.payload[1]); + } } else { this.targetValue = options.targetValue; this.duration = options.duration; } } - public targetValue: number; - public duration: Duration | undefined; + @ccValue() + @ccValueMetadata({ + ...ValueMetadata.ReadOnlyLevel, + label: "Current value", + }) + public readonly currentValue: Maybe | undefined; + + @ccValue({ forceCreation: true }) + @ccValueMetadata({ + ...ValueMetadata.Level, + label: "Target value", + }) + public readonly targetValue: number | undefined; + + @ccValue({ minVersion: 2 }) + @ccValueMetadata({ + ...ValueMetadata.ReadOnlyDuration, + label: "Remaining duration until target value", + }) + public readonly duration: Duration | undefined; public serialize(): Buffer { - const payload = [this.targetValue]; - if (this.version >= 2 && this.duration) { - payload.push(this.duration.serializeSet()); + const payload: number[] = [ + typeof this.currentValue !== "number" ? 0xfe : this.currentValue, + ]; + if (this.version >= 2 && this.targetValue && this.duration) { + payload.push(this.targetValue, this.duration.serializeReport()); } this.payload = Buffer.from(payload); return super.serialize(); diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index 879a37e07a54..cabf110901e3 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -75,6 +75,11 @@ import { getCompatEventValueId as getBasicCCCompatEventValueId, getCurrentValueValueId as getBasicCCCurrentValueValueId, } from "../commandclass/BasicCC"; + import { + MultilevelSwitchCC, + MultilevelSwitchCCSet, + getCompatEventValueId as getMultilevelSwitchCCCompatEventValueId, + } from "../commandclass/MultilevelSwitchCC"; import { CentralSceneCCNotification, CentralSceneKeys, @@ -2239,6 +2244,8 @@ protocol version: ${this._protocolVersion}`; if (command instanceof BasicCC) { return this.handleBasicCommand(command); + } else if (command instanceof MultilevelSwitchCC) { + return this.handleMultilevelSwitchCommand(command); } else if (command instanceof CentralSceneCCNotification) { return this.handleCentralSceneNotification(command); } else if (command instanceof WakeUpCCWakeUpNotification) { @@ -2810,6 +2817,28 @@ protocol version: ${this._protocolVersion}`; } } } + + + /** Handles the receipt of a MultilevelCC Set or Report */ + private handleMultilevelSwitchCommand(command: MultilevelSwitchCC): void { + // Retrieve the endpoint the command is coming from + const sourceEndpoint = + this.getEndpoint(command.endpointIndex ?? 0) ?? this; + if (command instanceof MultilevelSwitchCCSet) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: "treating BasicCC::Set as a value event", + }); + this._valueDB.setValue( + getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), + command.targetValue, + { + stateful: false, + }, + ); + return; + } + } /** * Allows automatically resetting notification values to idle if the node does not do it itself From fb7a000976743e6338c47ac8b2cb40495de65c59 Mon Sep 17 00:00:00 2001 From: Reuben Bijl Date: Fri, 25 Feb 2022 20:51:59 +1300 Subject: [PATCH 3/8] Added initial event support for MultilevelSwitchCCStartLevelChange and MultilevelSwitchCCStopLevelChange --- .../lib/commandclass/MultilevelSwitchCC.ts | 18 +++-- packages/zwave-js/src/lib/node/Node.ts | 71 +++++++++++++------ 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts index 2526a5672ae5..74fe3ad7d14f 100644 --- a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts +++ b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts @@ -1,12 +1,13 @@ -import type { MessageRecord, ValueID } from "@zwave-js/core"; import { CommandClasses, Duration, Maybe, MessageOrCCLogEntry, + MessageRecord, parseMaybeNumber, parseNumber, validatePayload, + ValueID, ValueMetadata, ZWaveError, ZWaveErrorCodes, @@ -749,11 +750,16 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { ) { super(driver, options); if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); + validatePayload(this.payload.length >= 3); + const direction = (this.payload[0] & 0b0_1_0_00000) >>> 6; + const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5; + const startLevel = this.payload[1]; + const duration = this.payload[2]; + + this.duration = Duration.parseReport(duration); + this.ignoreStartLevel = !!ignoreStartLevel; + this.startLevel = startLevel; + this.direction = direction ? "down" : "up"; } else { this.duration = options.duration; this.ignoreStartLevel = options.ignoreStartLevel; diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index cabf110901e3..31e75b5ebca0 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -75,11 +75,6 @@ import { getCompatEventValueId as getBasicCCCompatEventValueId, getCurrentValueValueId as getBasicCCCurrentValueValueId, } from "../commandclass/BasicCC"; - import { - MultilevelSwitchCC, - MultilevelSwitchCCSet, - getCompatEventValueId as getMultilevelSwitchCCCompatEventValueId, - } from "../commandclass/MultilevelSwitchCC"; import { CentralSceneCCNotification, CentralSceneKeys, @@ -115,6 +110,13 @@ import { getEndpointDeviceClassValueId, getEndpointIndizesValueId, } from "../commandclass/MultiChannelCC"; +import { + getCompatEventValueId as getMultilevelSwitchCCCompatEventValueId, + MultilevelSwitchCC, + MultilevelSwitchCCSet, + MultilevelSwitchCCStartLevelChange, + MultilevelSwitchCCStopLevelChange, +} from "../commandclass/MultilevelSwitchCC"; import { getNodeLocationValueId, getNodeNameValueId, @@ -2817,26 +2819,55 @@ protocol version: ${this._protocolVersion}`; } } } - - + /** Handles the receipt of a MultilevelCC Set or Report */ private handleMultilevelSwitchCommand(command: MultilevelSwitchCC): void { // Retrieve the endpoint the command is coming from const sourceEndpoint = this.getEndpoint(command.endpointIndex ?? 0) ?? this; - if (command instanceof MultilevelSwitchCCSet) { - this.driver.controllerLog.logNode(this.id, { - endpoint: command.endpointIndex, - message: "treating BasicCC::Set as a value event", - }); - this._valueDB.setValue( - getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), - command.targetValue, - { - stateful: false, - }, - ); - return; + if (command instanceof MultilevelSwitchCCSet) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: "treating MultiLevelSwitchCCSet::Set as a value event", + }); + this._valueDB.setValue( + getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), + command.targetValue, + { + stateful: false, + }, + ); + return; + } + if (command instanceof MultilevelSwitchCCStartLevelChange) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: + "treating MultilevelSwitchCCStartLevelChange::Set as a value event", + }); + this._valueDB.setValue( + getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), + command.direction, + { + stateful: false, + }, + ); + return; + } + if (command instanceof MultilevelSwitchCCStopLevelChange) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: + "treating MultilevelSwitchCCStopLevelChange::Set as a value event", + }); + this._valueDB.setValue( + getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), + "stop", + { + stateful: false, + }, + ); + return; } } From fbf0efdf1436d74933f76bae8dca9b2d5e16be87 Mon Sep 17 00:00:00 2001 From: Reuben Bijl Date: Thu, 3 Mar 2022 22:03:28 +1300 Subject: [PATCH 4/8] Updated with type/class changes from feedback --- maintenance/schemas/device-config.json | 3 + packages/config/src/CompatConfig.ts | 14 +++++ .../lib/commandclass/MultilevelSwitchCC.ts | 60 ++++++++++--------- .../zwave-js/src/lib/commandclass/index.ts | 6 +- packages/zwave-js/src/lib/node/Node.ts | 27 ++++----- packages/zwave-js/src/lib/node/Types.ts | 2 + 6 files changed, 67 insertions(+), 45 deletions(-) diff --git a/maintenance/schemas/device-config.json b/maintenance/schemas/device-config.json index b70a44e0d3cd..a9f69e08fa9f 100644 --- a/maintenance/schemas/device-config.json +++ b/maintenance/schemas/device-config.json @@ -282,6 +282,9 @@ "treatBasicSetAsEvent": { "const": true }, + "treatMultilevelSwitchAsEvent": { + "const": true + }, "treatDestinationEndpointAsSource": { "const": true } diff --git a/packages/config/src/CompatConfig.ts b/packages/config/src/CompatConfig.ts index 1d022458bde4..b6f94c11b0dd 100644 --- a/packages/config/src/CompatConfig.ts +++ b/packages/config/src/CompatConfig.ts @@ -187,6 +187,19 @@ error in compat option treatBasicSetAsEvent`, this.treatBasicSetAsEvent = definition.treatBasicSetAsEvent; } + if (definition.treatMultilevelSwitchAsEvent != undefined) { + if (definition.treatMultilevelSwitchAsEvent !== true) { + throwInvalidConfig( + "devices", + `config/devices/${filename}: + error in compat option treatMultilevelSwitchAsEvent`, + ); + } + + this.treatMultilevelSwitchAsEvent = + definition.treatMultilevelSwitchAsEvent; + } + if (definition.treatDestinationEndpointAsSource != undefined) { if (definition.treatDestinationEndpointAsSource !== true) { throwInvalidConfig( @@ -459,6 +472,7 @@ compat option alarmMapping must be an array where all items are objects!`, public readonly preserveEndpoints?: "*" | readonly number[]; public readonly skipConfigurationInfoQuery?: boolean; public readonly treatBasicSetAsEvent?: boolean; + public readonly treatMultilevelSwitchAsEvent?: boolean; public readonly treatDestinationEndpointAsSource?: boolean; public readonly queryOnWakeup?: readonly [ string, diff --git a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts index 74fe3ad7d14f..c508f84b3b98 100644 --- a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts +++ b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts @@ -15,6 +15,7 @@ import { import { getEnumMemberName, pick } from "@zwave-js/shared"; import type { Driver } from "../driver/Driver"; import { MessagePriority } from "../message/Constants"; +import type { ZWaveNode } from "../node/Node"; import { VirtualEndpoint } from "../node/VirtualEndpoint"; import { CCAPI, @@ -118,6 +119,25 @@ export function getCompatEventValueId(endpoint?: number): ValueID { }; } +/** + * @publicAPI + * This is emitted when a start or stop event is received + */ +export interface ZWaveNotificationCallbackArgs_MultilevelSwitchCC { + eventType: MultilevelSwitchCommand; + direction?: string; +} + +/** + * @publicAPI + * Parameter types for the MultilevelSwitch CC specific version of ZWaveNotificationCallback + */ +export type ZWaveNotificationCallbackParams_MultilevelSwitchCC = [ + node: ZWaveNode, + ccId: typeof CommandClasses["Multilevel Switch"], + args: ZWaveNotificationCallbackArgs_MultilevelSwitchCC, +]; + @API(CommandClasses["Multilevel Switch"]) export class MultilevelSwitchCCAPI extends CCAPI { public supportsCommand(cmd: MultilevelSwitchCommand): Maybe { @@ -507,7 +527,7 @@ export class MultilevelSwitchCC extends CommandClass { await this.refreshValues(); // create compat event value if necessary - if (node.deviceConfig?.compat?.treatBasicSetAsEvent) { + if (node.deviceConfig?.compat?.treatMultilevelSwitchAsEvent) { const valueId = getCompatEventValueId(this.endpointIndex); if (!node.valueDB.hasMetadata(valueId)) { node.valueDB.setMetadata(valueId, { @@ -602,10 +622,7 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { super(driver, options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); - this.currentValue = parseMaybeNumber( - this.payload[0], - driver.options.preserveUnknownValues, - ); + this.targetValue = parseNumber(this.payload[0]); if (this.version >= 2 && this.payload.length >= 2) { this.targetValue = parseNumber(this.payload[0]); @@ -617,33 +634,15 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { } } - @ccValue() - @ccValueMetadata({ - ...ValueMetadata.ReadOnlyLevel, - label: "Current value", - }) - public readonly currentValue: Maybe | undefined; - - @ccValue({ forceCreation: true }) - @ccValueMetadata({ - ...ValueMetadata.Level, - label: "Target value", - }) - public readonly targetValue: number | undefined; - - @ccValue({ minVersion: 2 }) - @ccValueMetadata({ - ...ValueMetadata.ReadOnlyDuration, - label: "Remaining duration until target value", - }) - public readonly duration: Duration | undefined; + public targetValue: number | undefined; + public duration: Duration | undefined; public serialize(): Buffer { - const payload: number[] = [ - typeof this.currentValue !== "number" ? 0xfe : this.currentValue, + const payload = [ + typeof this.targetValue !== "number" ? 0xfe : this.targetValue, ]; - if (this.version >= 2 && this.targetValue && this.duration) { - payload.push(this.targetValue, this.duration.serializeReport()); + if (this.version >= 2 && this.duration) { + payload.push(this.duration.serializeSet()); } this.payload = Buffer.from(payload); return super.serialize(); @@ -760,14 +759,17 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { this.ignoreStartLevel = !!ignoreStartLevel; this.startLevel = startLevel; this.direction = direction ? "down" : "up"; + this.eventType = MultilevelSwitchCommand.StartLevelChange; } else { this.duration = options.duration; this.ignoreStartLevel = options.ignoreStartLevel; this.startLevel = options.startLevel ?? 0; this.direction = options.direction; + this.eventType = MultilevelSwitchCommand.StartLevelChange; } } + public eventType: MultilevelSwitchCommand; public duration: Duration | undefined; public startLevel: number; public ignoreStartLevel: boolean; diff --git a/packages/zwave-js/src/lib/commandclass/index.ts b/packages/zwave-js/src/lib/commandclass/index.ts index c7eece77e537..9e88355d99a1 100644 --- a/packages/zwave-js/src/lib/commandclass/index.ts +++ b/packages/zwave-js/src/lib/commandclass/index.ts @@ -319,7 +319,11 @@ export { MultilevelSwitchCCSupportedReport, SwitchType, } from "./MultilevelSwitchCC"; -export type { MultilevelSwitchLevelChangeMetadata } from "./MultilevelSwitchCC"; +export type { + MultilevelSwitchLevelChangeMetadata, + ZWaveNotificationCallbackArgs_MultilevelSwitchCC, + ZWaveNotificationCallbackParams_MultilevelSwitchCC, +} from "./MultilevelSwitchCC"; export { NodeNamingAndLocationCC, NodeNamingAndLocationCCLocationGet, diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index 31e75b5ebca0..48fbd37b119c 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -116,6 +116,7 @@ import { MultilevelSwitchCCSet, MultilevelSwitchCCStartLevelChange, MultilevelSwitchCCStopLevelChange, + MultilevelSwitchCommand, } from "../commandclass/MultilevelSwitchCC"; import { getNodeLocationValueId, @@ -2822,9 +2823,6 @@ protocol version: ${this._protocolVersion}`; /** Handles the receipt of a MultilevelCC Set or Report */ private handleMultilevelSwitchCommand(command: MultilevelSwitchCC): void { - // Retrieve the endpoint the command is coming from - const sourceEndpoint = - this.getEndpoint(command.endpointIndex ?? 0) ?? this; if (command instanceof MultilevelSwitchCCSet) { this.driver.controllerLog.logNode(this.id, { endpoint: command.endpointIndex, @@ -2845,12 +2843,12 @@ protocol version: ${this._protocolVersion}`; message: "treating MultilevelSwitchCCStartLevelChange::Set as a value event", }); - this._valueDB.setValue( - getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), - command.direction, - { - stateful: false, - }, + // Notify listeners + this.emit( + "notification", + this, + CommandClasses["Multilevel Switch"], + pick(command, ["eventType", "direction"]), ); return; } @@ -2860,12 +2858,11 @@ protocol version: ${this._protocolVersion}`; message: "treating MultilevelSwitchCCStopLevelChange::Set as a value event", }); - this._valueDB.setValue( - getMultilevelSwitchCCCompatEventValueId(command.endpointIndex), - "stop", - { - stateful: false, - }, + this.emit( + "notification", + this, + CommandClasses["Multilevel Switch"], + { eventType: MultilevelSwitchCommand.StopLevelChange }, ); return; } diff --git a/packages/zwave-js/src/lib/node/Types.ts b/packages/zwave-js/src/lib/node/Types.ts index 5bbea14b9a1b..86ef8a0e5c5e 100644 --- a/packages/zwave-js/src/lib/node/Types.ts +++ b/packages/zwave-js/src/lib/node/Types.ts @@ -8,6 +8,7 @@ import type { } from "@zwave-js/core"; import type { FirmwareUpdateStatus } from "../commandclass"; import type { ZWaveNotificationCallbackParams_EntryControlCC } from "../commandclass/EntryControlCC"; +import type { ZWaveNotificationCallbackParams_MultilevelSwitchCC } from "../commandclass/MultilevelSwitchCC"; import type { ZWaveNotificationCallbackParams_NotificationCC } from "../commandclass/NotificationCC"; import type { Powerlevel, @@ -87,6 +88,7 @@ export type ZWaveNotificationCallback = ( | ZWaveNotificationCallbackParams_NotificationCC | ZWaveNotificationCallbackParams_EntryControlCC | ZWaveNotificationCallbackParams_PowerlevelCC + | ZWaveNotificationCallbackParams_MultilevelSwitchCC ) => void; export interface ZWaveNodeValueEventCallbacks { From 0c69a322c89055ca57613c9fe5c5b3e60241479d Mon Sep 17 00:00:00 2001 From: Reuben Bijl Date: Sun, 13 Mar 2022 19:14:26 +1300 Subject: [PATCH 5/8] Applied suggestions from code review, thanks @AlCalzone --- maintenance/schemas/device-config.json | 2 +- packages/config/src/CompatConfig.ts | 12 ++++++------ .../src/lib/commandclass/MultilevelSwitchCC.ts | 16 ++++++---------- packages/zwave-js/src/lib/node/Node.ts | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/maintenance/schemas/device-config.json b/maintenance/schemas/device-config.json index ad168c61cb2d..4d805805c8d4 100644 --- a/maintenance/schemas/device-config.json +++ b/maintenance/schemas/device-config.json @@ -282,7 +282,7 @@ "treatBasicSetAsEvent": { "const": true }, - "treatMultilevelSwitchAsEvent": { + "treatMultilevelSwitchSetAsEvent": { "const": true }, "treatDestinationEndpointAsSource": { diff --git a/packages/config/src/CompatConfig.ts b/packages/config/src/CompatConfig.ts index b6f94c11b0dd..c9f3e29cfefb 100644 --- a/packages/config/src/CompatConfig.ts +++ b/packages/config/src/CompatConfig.ts @@ -187,17 +187,17 @@ error in compat option treatBasicSetAsEvent`, this.treatBasicSetAsEvent = definition.treatBasicSetAsEvent; } - if (definition.treatMultilevelSwitchAsEvent != undefined) { - if (definition.treatMultilevelSwitchAsEvent !== true) { + if (definition.treatMultilevelSwitchSetAsEvent != undefined) { + if (definition.treatMultilevelSwitchSetAsEvent !== true) { throwInvalidConfig( "devices", `config/devices/${filename}: - error in compat option treatMultilevelSwitchAsEvent`, + error in compat option treatMultilevelSwitchSetAsEvent`, ); } - this.treatMultilevelSwitchAsEvent = - definition.treatMultilevelSwitchAsEvent; + this.treatMultilevelSwitchSetAsEvent = + definition.treatMultilevelSwitchSetAsEvent; } if (definition.treatDestinationEndpointAsSource != undefined) { @@ -472,7 +472,7 @@ compat option alarmMapping must be an array where all items are objects!`, public readonly preserveEndpoints?: "*" | readonly number[]; public readonly skipConfigurationInfoQuery?: boolean; public readonly treatBasicSetAsEvent?: boolean; - public readonly treatMultilevelSwitchAsEvent?: boolean; + public readonly treatMultilevelSwitchSetAsEvent?: boolean; public readonly treatDestinationEndpointAsSource?: boolean; public readonly queryOnWakeup?: readonly [ string, diff --git a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts index c508f84b3b98..7e5995990811 100644 --- a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts +++ b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts @@ -124,7 +124,7 @@ export function getCompatEventValueId(endpoint?: number): ValueID { * This is emitted when a start or stop event is received */ export interface ZWaveNotificationCallbackArgs_MultilevelSwitchCC { - eventType: MultilevelSwitchCommand; + eventType: MultilevelSwitchCommand.StartLevelChange | MultilevelSwitchCommand.StopLevelChange; direction?: string; } @@ -527,7 +527,7 @@ export class MultilevelSwitchCC extends CommandClass { await this.refreshValues(); // create compat event value if necessary - if (node.deviceConfig?.compat?.treatMultilevelSwitchAsEvent) { + if (node.deviceConfig?.compat?.treatMultilevelSwitchSetAsEvent) { const valueId = getCompatEventValueId(this.endpointIndex); if (!node.valueDB.hasMetadata(valueId)) { node.valueDB.setMetadata(valueId, { @@ -622,10 +622,9 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { super(driver, options); if (gotDeserializationOptions(options)) { validatePayload(this.payload.length >= 1); - this.targetValue = parseNumber(this.payload[0]); + this.targetValue = this.payload[0]; - if (this.version >= 2 && this.payload.length >= 2) { - this.targetValue = parseNumber(this.payload[0]); + if (this.payload.length >= 2) { this.duration = Duration.parseReport(this.payload[1]); } } else { @@ -634,12 +633,12 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { } } - public targetValue: number | undefined; + public targetValue: number; public duration: Duration | undefined; public serialize(): Buffer { const payload = [ - typeof this.targetValue !== "number" ? 0xfe : this.targetValue, + this.targetValue, ]; if (this.version >= 2 && this.duration) { payload.push(this.duration.serializeSet()); @@ -759,17 +758,14 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { this.ignoreStartLevel = !!ignoreStartLevel; this.startLevel = startLevel; this.direction = direction ? "down" : "up"; - this.eventType = MultilevelSwitchCommand.StartLevelChange; } else { this.duration = options.duration; this.ignoreStartLevel = options.ignoreStartLevel; this.startLevel = options.startLevel ?? 0; this.direction = options.direction; - this.eventType = MultilevelSwitchCommand.StartLevelChange; } } - public eventType: MultilevelSwitchCommand; public duration: Duration | undefined; public startLevel: number; public ignoreStartLevel: boolean; diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index cd60a7ef6e06..b8379426f6c2 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -2884,7 +2884,7 @@ protocol version: ${this.protocolVersion}`; "notification", this, CommandClasses["Multilevel Switch"], - pick(command, ["eventType", "direction"]), + { eventType: MultilevelSwitchCommand.StartLevelChange, direction: command.direction }, ); return; } From 414244d98ed5a4906b3b8b66ee153bdd9cf57255 Mon Sep 17 00:00:00 2001 From: Z-Wave JS Bot Date: Sun, 13 Mar 2022 06:19:13 +0000 Subject: [PATCH 6/8] style: fix lint --- .../zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts | 8 ++++---- packages/zwave-js/src/lib/node/Node.ts | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts index 7e5995990811..04440d0c15c9 100644 --- a/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts +++ b/packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts @@ -124,7 +124,9 @@ export function getCompatEventValueId(endpoint?: number): ValueID { * This is emitted when a start or stop event is received */ export interface ZWaveNotificationCallbackArgs_MultilevelSwitchCC { - eventType: MultilevelSwitchCommand.StartLevelChange | MultilevelSwitchCommand.StopLevelChange; + eventType: + | MultilevelSwitchCommand.StartLevelChange + | MultilevelSwitchCommand.StopLevelChange; direction?: string; } @@ -637,9 +639,7 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { public duration: Duration | undefined; public serialize(): Buffer { - const payload = [ - this.targetValue, - ]; + const payload = [this.targetValue]; if (this.version >= 2 && this.duration) { payload.push(this.duration.serializeSet()); } diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index b8379426f6c2..aba7291ce4a0 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -2884,7 +2884,10 @@ protocol version: ${this.protocolVersion}`; "notification", this, CommandClasses["Multilevel Switch"], - { eventType: MultilevelSwitchCommand.StartLevelChange, direction: command.direction }, + { + eventType: MultilevelSwitchCommand.StartLevelChange, + direction: command.direction, + }, ); return; } From 0180109950cf0cc34d4b65d2d8abb154cff93f78 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Mon, 14 Mar 2022 15:27:00 +0100 Subject: [PATCH 7/8] fix: nits --- packages/zwave-js/src/lib/node/Node.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index aba7291ce4a0..19482729343b 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -2871,15 +2871,12 @@ protocol version: ${this.protocolVersion}`; stateful: false, }, ); - return; - } - if (command instanceof MultilevelSwitchCCStartLevelChange) { + } else if (command instanceof MultilevelSwitchCCStartLevelChange) { this.driver.controllerLog.logNode(this.id, { endpoint: command.endpointIndex, message: - "treating MultilevelSwitchCCStartLevelChange::Set as a value event", + "treating MultilevelSwitchCC::StartLevelChange as a notification", }); - // Notify listeners this.emit( "notification", this, @@ -2889,13 +2886,11 @@ protocol version: ${this.protocolVersion}`; direction: command.direction, }, ); - return; - } - if (command instanceof MultilevelSwitchCCStopLevelChange) { + } else if (command instanceof MultilevelSwitchCCStopLevelChange) { this.driver.controllerLog.logNode(this.id, { endpoint: command.endpointIndex, message: - "treating MultilevelSwitchCCStopLevelChange::Set as a value event", + "treating MultilevelSwitchCC::StopLevelChange as a notification", }); this.emit( "notification", @@ -2903,7 +2898,6 @@ protocol version: ${this.protocolVersion}`; CommandClasses["Multilevel Switch"], { eventType: MultilevelSwitchCommand.StopLevelChange }, ); - return; } } From 398de14f98b671c4f97efc69247b124e3268284f Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Mon, 14 Mar 2022 15:32:45 +0100 Subject: [PATCH 8/8] docs: explain compat flag --- docs/config-files/file-format.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/config-files/file-format.md b/docs/config-files/file-format.md index f5c525e8ac13..679ce2439901 100644 --- a/docs/config-files/file-format.md +++ b/docs/config-files/file-format.md @@ -397,11 +397,15 @@ Some devices spam the network with hundreds of invalid `ConfigurationCC::InfoRep ### `treatBasicSetAsEvent` -By default, `Basic CC::Set` commands are interpreted as status updates. This flag causes the driver to emit a `value event` for the `"value"` property instead. Note that this property is exclusively used in this case in order to avoid conflicts with regular value IDs. +By default, `Basic CC::Set` commands are interpreted as status updates. This flag causes the driver to emit a `value event` for the `"event"` property instead. Note that this property is exclusively used in this case in order to avoid conflicts with regular value IDs. > [!NOTE] > If this option is `true`, it has precedence over `disableBasicMapping`. +### `treatMultilevelSwitchSetAsEvent` + +By default, `Multilevel Switch CC::Set` commands are ignored, because they are meant to control end devices. This flag causes the driver to emit a `value event` for the `"event"` property instead, so applications can react to these commands, e.g. for remotes. + ### `treatDestinationEndpointAsSource` Some devices incorrectly use the multi channel **destination** endpoint in reports to indicate the **source** endpoint the report originated from. When this flag is `true`, the destination endpoint is instead interpreted to be the source and the original source endpoint gets ignored.