From 79a62cc73b04ddd24a3e64c53a9cb86a388cc7c2 Mon Sep 17 00:00:00 2001 From: XLDom <90308906+XLDom@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:27:56 +0200 Subject: [PATCH] feat: add compat flag to treat `Set` commands as `Report`, enable for SRT321 HRT4-ZW (#6423) Co-authored-by: Dominic Griesel --- .vscode/deviceSnippets.code-snippets | 2 +- docs/config-files/file-format.md | 7 +++ maintenance/schemas/device-config.json | 8 +++ .../config/config/devices/0x0059/hrt4-zw.json | 6 +- packages/config/src/devices/CompatConfig.ts | 19 +++++++ packages/config/src/devices/DeviceConfig.ts | 3 + packages/zwave-js/src/lib/node/Node.ts | 56 +++++++++++++++++++ 7 files changed, 99 insertions(+), 2 deletions(-) diff --git a/.vscode/deviceSnippets.code-snippets b/.vscode/deviceSnippets.code-snippets index 780e994c48ae..92b32321eb95 100644 --- a/.vscode/deviceSnippets.code-snippets +++ b/.vscode/deviceSnippets.code-snippets @@ -135,7 +135,7 @@ "body": [ "\"compat\": {", "\t$LINE_COMMENT This device incorrectly ${1:[problem]}", - "\t\"${2|disableBasicMapping,disableStrictEntryControlDataValidation,disableStrictMeasurementValidation,enableBasicSetMapping,forceNotificationIdleReset,mapRootReportsToEndpoint,preserveRootApplicationCCValueIDs,skipConfigurationNameQuery,skipConfigurationInfoQuery,treatBasicSetAsEvent,treatMultilevelSwitchSetAsEvent,treatDestinationEndpointAsSource|}\": true", + "\t\"${2|disableBasicMapping,disableStrictEntryControlDataValidation,disableStrictMeasurementValidation,enableBasicSetMapping,forceNotificationIdleReset,mapRootReportsToEndpoint,preserveRootApplicationCCValueIDs,skipConfigurationNameQuery,skipConfigurationInfoQuery,treatBasicSetAsEvent,treatMultilevelSwitchSetAsEvent,treatSetAsReport,treatDestinationEndpointAsSource|}\": true", "}," ], "description": "Insert parameter condition" diff --git a/docs/config-files/file-format.md b/docs/config-files/file-format.md index e6e92cb4d03a..a5615bff5e56 100644 --- a/docs/config-files/file-format.md +++ b/docs/config-files/file-format.md @@ -503,6 +503,13 @@ By default, `Basic CC::Set` commands are interpreted as status updates. This fla 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. +### `treatSetAsReport` + +By default, many `Set` CC commands are ignored, because they are meant to control end devices. For some devices, those commands are the only way to receive updates about some values though. +This flag causes the driver treat the listed commands as a report instead and issue a `value report`, so applications can react to them. + +> [!NOTE] This mapping is CC specific and must be implemented for every CC that needs it. Currently, only `BinarySwitchCCSet` and `ThermostatModeCCSet` are supported. + ### `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. diff --git a/maintenance/schemas/device-config.json b/maintenance/schemas/device-config.json index c7a098982eea..491f3d86c847 100644 --- a/maintenance/schemas/device-config.json +++ b/maintenance/schemas/device-config.json @@ -702,6 +702,14 @@ "treatMultilevelSwitchSetAsEvent": { "const": true }, + "treatSetAsReport": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, "treatDestinationEndpointAsSource": { "const": true }, diff --git a/packages/config/config/devices/0x0059/hrt4-zw.json b/packages/config/config/devices/0x0059/hrt4-zw.json index c8baf3cd6e4a..426e87dfdcf9 100644 --- a/packages/config/config/devices/0x0059/hrt4-zw.json +++ b/packages/config/config/devices/0x0059/hrt4-zw.json @@ -90,5 +90,9 @@ "defaultValue": 10, "unsigned": true } - ] + ], + "compat": { + // The device "reports" some of its state with Set commands + "treatSetAsReport": ["BinarySwitchCCSet", "ThermostatModeCCSet"] + } } diff --git a/packages/config/src/devices/CompatConfig.ts b/packages/config/src/devices/CompatConfig.ts index b3d516dcd24c..49cc0ea17f26 100644 --- a/packages/config/src/devices/CompatConfig.ts +++ b/packages/config/src/devices/CompatConfig.ts @@ -286,6 +286,23 @@ error in compat option treatMultilevelSwitchSetAsEvent`, definition.treatMultilevelSwitchSetAsEvent; } + if (definition.treatSetAsReport != undefined) { + if ( + !(isArray(definition.treatSetAsReport) + && definition.treatSetAsReport.every( + (d: any) => typeof d === "string", + )) + ) { + throwInvalidConfig( + "devices", + `config/devices/${filename}: +compat option treatSetAsReport must be an array of strings`, + ); + } + + this.treatSetAsReport = new Set(definition.treatSetAsReport); + } + if (definition.treatDestinationEndpointAsSource != undefined) { if (definition.treatDestinationEndpointAsSource !== true) { throwInvalidConfig( @@ -616,6 +633,7 @@ compat option overrideQueries must be an object!`, public readonly skipConfigurationInfoQuery?: boolean; public readonly treatBasicSetAsEvent?: boolean; public readonly treatMultilevelSwitchSetAsEvent?: boolean; + public readonly treatSetAsReport?: ReadonlySet; public readonly treatDestinationEndpointAsSource?: boolean; public readonly useUTCInTimeParametersCC?: boolean; public readonly queryOnWakeup?: readonly [ @@ -657,6 +675,7 @@ compat option overrideQueries must be an object!`, "skipConfigurationInfoQuery", "treatBasicSetAsEvent", "treatMultilevelSwitchSetAsEvent", + "treatSetAsReport", "treatDestinationEndpointAsSource", "useUTCInTimeParametersCC", "queryOnWakeup", diff --git a/packages/config/src/devices/DeviceConfig.ts b/packages/config/src/devices/DeviceConfig.ts index d7710ebcc547..4375e01027fc 100644 --- a/packages/config/src/devices/DeviceConfig.ts +++ b/packages/config/src/devices/DeviceConfig.ts @@ -890,6 +890,9 @@ export class DeviceConfig { if (this.compat.removeCCs) { c.removeCCs = Object.fromEntries(this.compat.removeCCs); } + if (this.compat.treatSetAsReport) { + c.treatSetAsReport = [...this.compat.treatSetAsReport].sort(); + } c = sortObject(c); if (Object.keys(c).length > 0) { diff --git a/packages/zwave-js/src/lib/node/Node.ts b/packages/zwave-js/src/lib/node/Node.ts index 549af4cb346d..812ef28983bd 100644 --- a/packages/zwave-js/src/lib/node/Node.ts +++ b/packages/zwave-js/src/lib/node/Node.ts @@ -60,6 +60,11 @@ import { BasicCCSet, BasicCCValues, } from "@zwave-js/cc/BasicCC"; +import { + type BinarySwitchCC, + BinarySwitchCCSet, + BinarySwitchCCValues, +} from "@zwave-js/cc/BinarySwitchCC"; import { CentralSceneCCNotification, CentralSceneCCValues, @@ -106,6 +111,11 @@ import { SecurityCCNonceGet, SecurityCCNonceReport, } from "@zwave-js/cc/SecurityCC"; +import { + type ThermostatModeCC, + ThermostatModeCCSet, + ThermostatModeCCValues, +} from "@zwave-js/cc/ThermostatModeCC"; import { VersionCCCommandClassGet, VersionCCGet, @@ -3009,6 +3019,10 @@ protocol version: ${this.protocolVersion}`; return this.handleBasicCommand(command); } else if (command instanceof MultilevelSwitchCC) { return this.handleMultilevelSwitchCommand(command); + } else if (command instanceof BinarySwitchCCSet) { + return this.handleBinarySwitchCommand(command); + } else if (command instanceof ThermostatModeCCSet) { + return this.handleThermostatModeCommand(command); } else if (command instanceof CentralSceneCCNotification) { return this.handleCentralSceneNotification(command); } else if (command instanceof WakeUpCCWakeUpNotification) { @@ -3731,6 +3745,48 @@ protocol version: ${this.protocolVersion}`; } } + private handleBinarySwitchCommand(command: BinarySwitchCC): void { + // Treat BinarySwitchCCSet as a report if desired + if ( + command instanceof BinarySwitchCCSet + && this._deviceConfig?.compat?.treatSetAsReport?.has( + command.constructor.name, + ) + ) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: "treating BinarySwitchCC::Set as a report", + }); + this._valueDB.setValue( + BinarySwitchCCValues.currentValue.endpoint( + command.endpointIndex, + ), + command.targetValue, + ); + } + } + + private handleThermostatModeCommand(command: ThermostatModeCC): void { + // Treat ThermostatModeCCSet as a report if desired + if ( + command instanceof ThermostatModeCCSet + && this._deviceConfig?.compat?.treatSetAsReport?.has( + command.constructor.name, + ) + ) { + this.driver.controllerLog.logNode(this.id, { + endpoint: command.endpointIndex, + message: "treating ThermostatModeCC::Set as a report", + }); + this._valueDB.setValue( + ThermostatModeCCValues.thermostatMode.endpoint( + command.endpointIndex, + ), + command.mode, + ); + } + } + private async handleZWavePlusGet(command: ZWavePlusCCGet): Promise { const endpoint = this.getEndpoint(command.endpointIndex) ?? this;