From 249567dcee24b5fc2cb5e8c2896863305110d528 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Fri, 25 Oct 2024 10:09:57 +0200 Subject: [PATCH] refactor: decouple CCs and messages from host, split parsing and creation, split ZWaveNode class (#7305) --- .vscode/typescript.code-snippets | 642 +- docs/api/endpoint.md | 4 +- docs/getting-started/migrating/README.md | 1 + docs/getting-started/migrating/_sidebar.md | 1 + docs/getting-started/migrating/v14.md | 195 + docs/usage/custom.md | 4 +- packages/cc/cc.api.md | 6355 ++++++++++------- packages/cc/package.json | 1 - packages/cc/src/cc/AlarmSensorCC.ts | 244 +- packages/cc/src/cc/AssociationCC.ts | 444 +- packages/cc/src/cc/AssociationGroupInfoCC.ts | 488 +- packages/cc/src/cc/BarrierOperatorCC.ts | 350 +- packages/cc/src/cc/BasicCC.ts | 209 +- packages/cc/src/cc/BatteryCC.ts | 248 +- packages/cc/src/cc/BinarySensorCC.ts | 232 +- packages/cc/src/cc/BinarySwitchCC.ts | 169 +- packages/cc/src/cc/CRC16CC.ts | 100 +- packages/cc/src/cc/CentralSceneCC.ts | 276 +- .../cc/src/cc/ClimateControlScheduleCC.ts | 280 +- packages/cc/src/cc/ClockCC.ts | 136 +- packages/cc/src/cc/ColorSwitchCC.ts | 458 +- packages/cc/src/cc/ConfigurationCC.ts | 1288 ++-- packages/cc/src/cc/DeviceResetLocallyCC.ts | 34 +- packages/cc/src/cc/DoorLockCC.ts | 610 +- packages/cc/src/cc/DoorLockLoggingCC.ts | 189 +- packages/cc/src/cc/EnergyProductionCC.ts | 144 +- packages/cc/src/cc/EntryControlCC.ts | 360 +- .../cc/src/cc/FirmwareUpdateMetaDataCC.ts | 708 +- packages/cc/src/cc/HumidityControlModeCC.ts | 192 +- .../src/cc/HumidityControlOperatingStateCC.ts | 74 +- .../cc/src/cc/HumidityControlSetpointCC.ts | 532 +- packages/cc/src/cc/InclusionControllerCC.ts | 124 +- packages/cc/src/cc/IndicatorCC.ts | 661 +- packages/cc/src/cc/IrrigationCC.ts | 1085 +-- packages/cc/src/cc/LanguageCC.ts | 129 +- packages/cc/src/cc/LockCC.ts | 117 +- .../cc/src/cc/ManufacturerProprietaryCC.ts | 130 +- packages/cc/src/cc/ManufacturerSpecificCC.ts | 185 +- packages/cc/src/cc/MeterCC.ts | 517 +- .../cc/src/cc/MultiChannelAssociationCC.ts | 472 +- packages/cc/src/cc/MultiChannelCC.ts | 856 ++- packages/cc/src/cc/MultiCommandCC.ts | 107 +- packages/cc/src/cc/MultilevelSensorCC.ts | 419 +- packages/cc/src/cc/MultilevelSwitchCC.ts | 311 +- packages/cc/src/cc/NoOperationCC.ts | 18 +- packages/cc/src/cc/NodeNamingCC.ts | 219 +- packages/cc/src/cc/NotificationCC.ts | 719 +- packages/cc/src/cc/PowerlevelCC.ts | 266 +- packages/cc/src/cc/ProtectionCC.ts | 393 +- packages/cc/src/cc/SceneActivationCC.ts | 69 +- .../cc/src/cc/SceneActuatorConfigurationCC.ts | 209 +- .../src/cc/SceneControllerConfigurationCC.ts | 239 +- packages/cc/src/cc/ScheduleEntryLockCC.ts | 1319 ++-- packages/cc/src/cc/Security2CC.ts | 2203 +++--- packages/cc/src/cc/SecurityCC.ts | 608 +- packages/cc/src/cc/SoundSwitchCC.ts | 387 +- packages/cc/src/cc/SupervisionCC.ts | 181 +- packages/cc/src/cc/ThermostatFanModeCC.ts | 190 +- packages/cc/src/cc/ThermostatFanStateCC.ts | 74 +- packages/cc/src/cc/ThermostatModeCC.ts | 302 +- .../cc/src/cc/ThermostatOperatingStateCC.ts | 75 +- packages/cc/src/cc/ThermostatSetbackCC.ts | 155 +- packages/cc/src/cc/ThermostatSetpointCC.ts | 571 +- packages/cc/src/cc/TimeCC.ts | 297 +- packages/cc/src/cc/TimeParametersCC.ts | 195 +- packages/cc/src/cc/TransportServiceCC.ts | 385 +- packages/cc/src/cc/UserCodeCC.ts | 1045 +-- packages/cc/src/cc/VersionCC.ts | 431 +- packages/cc/src/cc/WakeUpCC.ts | 217 +- packages/cc/src/cc/WindowCoveringCC.ts | 374 +- packages/cc/src/cc/ZWavePlusCC.ts | 94 +- packages/cc/src/cc/ZWaveProtocolCC.ts | 1320 ++-- packages/cc/src/cc/index.ts | 121 +- .../cc/manufacturerProprietary/FibaroCC.ts | 309 +- packages/cc/src/index.ts | 1 - packages/cc/src/lib/API.ts | 173 +- packages/cc/src/lib/CommandClass.ts | 653 +- .../cc/src/lib/EncapsulatingCommandClass.ts | 32 +- packages/cc/src/lib/ICommandClassContainer.ts | 14 - packages/cc/src/lib/Values.ts | 8 +- packages/cc/src/lib/_Types.ts | 1 + packages/cc/src/lib/utils.ts | 257 +- packages/cc/tsconfig.build.json | 3 - packages/core/core.api.md | 314 +- packages/core/package.json | 2 + .../core/src/abstractions/ICommandClass.ts | 29 - .../core/src/abstractions/IZWaveEndpoint.ts | 31 - packages/core/src/abstractions/IZWaveNode.ts | 31 - packages/core/src/consts/Transmission.ts | 6 +- packages/core/src/index.ts | 8 +- packages/core/src/index_safe.ts | 8 +- packages/core/src/security/SecurityClass.ts | 16 +- packages/core/src/traits/CommandClasses.ts | 62 + packages/core/src/traits/Endpoints.ts | 15 + packages/core/src/traits/Nodes.ts | 51 + packages/core/src/traits/SecurityManagers.ts | 12 + packages/core/src/util/compareVersions.ts | 47 + .../src/rules/consistent-cc-classes.ts | 82 +- .../src/rules/no-internal-cc-types.ts | 1 - packages/host/host.api.md | 219 +- packages/host/src/ZWaveHost.ts | 153 +- packages/host/src/mocks.ts | 222 +- packages/maintenance/src/generateTypedDocs.ts | 5 +- .../maintenance/src/refactorCCParsing.01.ts | 319 + .../maintenance/src/refactorCCParsing.02.ts | 126 + .../maintenance/src/refactorCCParsing.03.ts | 79 + .../maintenance/src/refactorCCParsing.04.ts | 69 + .../src/refactorMessageParsing.01.ts | 430 ++ .../src/refactorMessageParsing.02.ts | 68 + .../src/refactorMessageParsing.03.ts | 61 + packages/serial/package.json | 6 + packages/serial/serial.api.md | 124 +- packages/serial/src/index.ts | 25 +- packages/serial/src/index_mock.ts | 6 +- packages/serial/src/index_safe.ts | 4 +- packages/serial/src/index_serialapi.ts | 56 + packages/serial/src/{ => log}/Logger.ts | 2 +- packages/serial/src/{ => log}/Logger_safe.ts | 0 packages/serial/src/message/Constants.ts | 2 +- packages/serial/src/message/INodeQuery.ts | 10 - packages/serial/src/message/Message.test.ts | 244 +- packages/serial/src/message/Message.ts | 372 +- .../src/{ => message}/MessageHeaders.ts | 0 .../serial/src/message/ZnifferMessages.ts | 1 - .../serial/src/{ => mock}/MockSerialPort.ts | 4 +- .../src/{ => mock}/SerialPortBindingMock.ts | 0 .../serial/src/{ => mock}/SerialPortMock.ts | 3 +- .../serial/src/parsers/BootloaderParsers.ts | 4 +- .../serial/src/parsers/SerialAPIParser.ts | 4 +- packages/serial/src/parsers/ZnifferParser.ts | 4 +- .../ApplicationCommandRequest._test.ts | 0 .../application/ApplicationCommandRequest.ts | 203 + .../application/ApplicationUpdateRequest.ts | 228 +- .../BridgeApplicationCommandRequest.test.ts | 12 +- .../BridgeApplicationCommandRequest.ts | 143 +- .../application/SerialAPIStartedRequest.ts | 84 +- .../serialapi/application/ShutdownMessages.ts | 30 +- .../GetControllerCapabilitiesMessages.ts | 91 +- .../GetControllerVersionMessages.ts | 63 + .../capability/GetLongRangeNodesMessages.ts | 130 + .../capability/GetProtocolVersionMessages.ts | 78 + .../GetSerialApiCapabilitiesMessages.ts | 73 +- .../GetSerialApiInitDataMessages.ts | 150 +- .../serialapi/capability/HardResetRequest.ts | 69 + .../capability/LongRangeChannelMessages.ts | 167 + .../capability/SerialAPISetupMessages.test.ts | 13 +- .../capability/SerialAPISetupMessages.ts | 878 ++- .../SetApplicationNodeInformationRequest.ts | 9 +- .../SetLongRangeShadowNodeIDsRequest.ts | 48 +- .../memory/GetControllerIdMessages.ts | 48 +- .../misc/GetBackgroundRSSIMessages.ts | 44 +- .../misc/SetRFReceiveModeMessages.ts | 66 +- .../misc/SetSerialApiTimeoutsMessages.ts | 76 + .../src}/serialapi/misc/SoftResetRequest.ts | 0 .../src}/serialapi/misc/WatchdogMessages.ts | 0 .../network-mgmt/AddNodeToNetworkRequest.ts | 209 +- .../AssignPriorityReturnRouteMessages.ts | 153 +- .../AssignPrioritySUCReturnRouteMessages.ts | 144 +- .../network-mgmt/AssignReturnRouteMessages.ts | 194 + .../AssignSUCReturnRouteMessages.ts | 198 + .../network-mgmt/DeleteReturnRouteMessages.ts | 176 + .../DeleteSUCReturnRouteMessages.ts | 200 + .../GetNodeProtocolInfoMessages._test.ts | 0 .../GetNodeProtocolInfoMessages.ts | 193 + .../network-mgmt/GetPriorityRouteMessages.ts | 89 +- .../network-mgmt/GetRoutingInfoMessages.ts | 54 +- .../network-mgmt/GetSUCNodeIdMessages.ts | 46 +- .../network-mgmt/IsFailedNodeMessages.ts | 43 +- .../network-mgmt/RemoveFailedNodeMessages.ts | 104 +- .../RemoveNodeFromNetworkRequest.ts | 182 +- .../network-mgmt/ReplaceFailedNodeRequest.ts | 104 +- .../network-mgmt/RequestNodeInfoMessages.ts | 75 +- .../RequestNodeNeighborUpdateMessages.ts | 97 +- .../network-mgmt/SetLearnModeMessages.ts | 128 +- .../network-mgmt/SetPriorityRouteMessages.ts | 103 +- .../network-mgmt/SetSUCNodeIDMessages.ts | 186 + .../nvm/ExtNVMReadLongBufferMessages.ts | 84 +- .../nvm/ExtNVMReadLongByteMessages.ts | 70 +- .../nvm/ExtNVMWriteLongBufferMessages.ts | 86 +- .../nvm/ExtNVMWriteLongByteMessages.ts | 123 + .../nvm/ExtendedNVMOperationsMessages.ts | 186 +- .../nvm/FirmwareUpdateNVMMessages.ts | 371 +- .../src}/serialapi/nvm/GetNVMIdMessages.ts | 39 +- .../serialapi/nvm/NVMOperationsMessages.ts | 178 +- .../transport/SendDataBridgeMessages.ts | 569 ++ .../serialapi/transport/SendDataMessages.ts | 639 ++ .../serialapi/transport/SendDataShared.ts | 35 +- .../transport/SendTestFrameMessages.ts | 187 + packages/serial/src/serialapi/utils.ts | 54 + .../src/{ => serialport}/DisconnectError.ts | 0 .../{ => serialport}/ZWaveSerialPort.test.ts | 6 +- .../src/{ => serialport}/ZWaveSerialPort.ts | 0 .../{ => serialport}/ZWaveSerialPortBase.ts | 10 +- .../ZWaveSerialPortImplementation.ts | 0 .../src/{ => serialport}/ZWaveSocket.ts | 0 .../{ => serialport}/ZWaveSocketOptions.ts | 0 .../src/{ => zniffer}/ZnifferSerialPort.ts | 2 +- .../{ => zniffer}/ZnifferSerialPortBase.ts | 6 +- .../serial/src/{ => zniffer}/ZnifferSocket.ts | 2 +- packages/serial/tsconfig.build.json | 3 + packages/testing/src/MockController.ts | 175 +- packages/testing/src/MockNode.ts | 102 +- packages/testing/testing.api.md | 71 +- packages/zwave-js/src/Controller.ts | 7 +- packages/zwave-js/src/Controller_safe.ts | 2 +- packages/zwave-js/src/Driver.ts | 10 + .../Controller.manageAssociations.test.ts | 64 +- .../zwave-js/src/lib/controller/Controller.ts | 412 +- .../lib/controller/MockControllerBehaviors.ts | 316 +- packages/zwave-js/src/lib/controller/NVMIO.ts | 2 +- packages/zwave-js/src/lib/controller/utils.ts | 47 - packages/zwave-js/src/lib/driver/Driver.ts | 553 +- .../src/lib/driver/MessageGenerators.ts | 1182 +-- .../driver/SerialAPICommandMachine.test.ts | 8 +- .../src/lib/driver/SerialAPICommandMachine.ts | 7 +- .../src/lib/driver/StateMachineShared.ts | 20 +- .../src/lib/driver/Transaction.test.ts | 45 +- .../zwave-js/src/lib/driver/Transaction.ts | 8 +- packages/zwave-js/src/lib/log/Driver.test.ts | 8 +- packages/zwave-js/src/lib/log/Driver.ts | 10 +- packages/zwave-js/src/lib/node/Endpoint.ts | 30 +- .../src/lib/node/MockNodeBehaviors.ts | 30 +- .../src/lib/node/MultiCCAPIWrapper.ts | 6 +- packages/zwave-js/src/lib/node/Node.ts | 2149 +----- .../src/lib/node/NodeReadyMachine.test.ts | 2 - .../src/lib/node/NodeStatusMachine.ts | 6 +- .../zwave-js/src/lib/node/VirtualEndpoint.ts | 22 +- packages/zwave-js/src/lib/node/VirtualNode.ts | 5 +- .../zwave-js/src/lib/node/mixins/00_Base.ts | 14 + .../src/lib/node/mixins/01_NetworkRole.ts | 144 + .../src/lib/node/mixins/05_Security.ts | 80 + .../zwave-js/src/lib/node/mixins/10_Events.ts | 41 + .../zwave-js/src/lib/node/mixins/20_Status.ts | 196 + .../zwave-js/src/lib/node/mixins/30_Wakeup.ts | 76 + .../zwave-js/src/lib/node/mixins/40_Values.ts | 246 + .../src/lib/node/mixins/50_Endpoints.ts | 182 + .../src/lib/node/mixins/60_ScheduledPoll.ts | 189 + .../src/lib/node/mixins/70_FirmwareUpdate.ts | 993 +++ .../zwave-js/src/lib/node/mixins/README.md | 3 + .../zwave-js/src/lib/node/mixins/index.ts | 3 + .../src/lib/node/mockCCBehaviors/Basic.ts | 4 +- .../lib/node/mockCCBehaviors/BinarySensor.ts | 8 +- .../lib/node/mockCCBehaviors/BinarySwitch.ts | 4 +- .../lib/node/mockCCBehaviors/ColorSwitch.ts | 8 +- .../lib/node/mockCCBehaviors/Configuration.ts | 20 +- .../node/mockCCBehaviors/EnergyProduction.ts | 4 +- .../mockCCBehaviors/ManufacturerSpecific.ts | 2 +- .../src/lib/node/mockCCBehaviors/Meter.ts | 8 +- .../lib/node/mockCCBehaviors/MultiChannel.ts | 16 +- .../node/mockCCBehaviors/MultilevelSensor.ts | 12 +- .../node/mockCCBehaviors/MultilevelSwitch.ts | 8 +- .../lib/node/mockCCBehaviors/Notification.ts | 8 +- .../node/mockCCBehaviors/ScheduleEntryLock.ts | 31 +- .../lib/node/mockCCBehaviors/SoundSwitch.ts | 38 +- .../node/mockCCBehaviors/ThermostatMode.ts | 8 +- .../node/mockCCBehaviors/ThermostatSetback.ts | 4 +- .../mockCCBehaviors/ThermostatSetpoint.ts | 20 +- .../src/lib/node/mockCCBehaviors/UserCode.ts | 28 +- .../node/mockCCBehaviors/WindowCovering.ts | 4 +- packages/zwave-js/src/lib/node/utils.ts | 205 +- packages/zwave-js/src/lib/serialapi/_Types.ts | 2 - .../application/ApplicationCommandRequest.ts | 167 - .../GetControllerVersionMessages.ts | 60 - .../capability/GetLongRangeNodesMessages.ts | 119 - .../capability/GetProtocolVersionMessages.ts | 48 - .../serialapi/capability/HardResetRequest.ts | 72 - .../capability/LongRangeChannelMessages.ts | 122 - .../misc/SetSerialApiTimeoutsMessages.ts | 64 - .../network-mgmt/AssignReturnRouteMessages.ts | 155 - .../AssignSUCReturnRouteMessages.ts | 198 - .../network-mgmt/DeleteReturnRouteMessages.ts | 137 - .../DeleteSUCReturnRouteMessages.ts | 197 - .../GetNodeProtocolInfoMessages.ts | 170 - .../network-mgmt/SetSUCNodeIDMessages.ts | 147 - .../nvm/ExtNVMWriteLongByteMessages.ts | 105 - .../transport/SendDataBridgeMessages.ts | 481 -- .../serialapi/transport/SendDataMessages.ts | 603 -- .../transport/SendTestFrameMessages.ts | 150 - .../src/lib/telemetry/deviceConfig.ts | 3 +- packages/zwave-js/src/lib/test/assertCC.ts | 4 +- .../discardUnsupportedReports.test.ts | 28 +- .../mapNotificationDoorLock.test.ts | 8 +- .../cc-specific/notificationEnums.test.ts | 84 +- .../notificationIdleManually.test.ts | 16 +- .../notificationIdleRelated.test.ts | 8 +- .../cc-specific/undefinedTargetValue.test.ts | 4 +- .../cc-specific/unknownNotifications.test.ts | 4 +- packages/zwave-js/src/lib/test/cc/API.test.ts | 2 +- .../src/lib/test/cc/AssociationCC.test.ts | 44 +- .../test/cc/AssociationGroupInfoCC.test.ts | 59 +- .../zwave-js/src/lib/test/cc/BasicCC.test.ts | 109 +- .../src/lib/test/cc/BatteryCC.test.ts | 65 +- .../src/lib/test/cc/BinarySensorCC.test.ts | 51 +- .../src/lib/test/cc/BinarySwitchCC.test.ts | 57 +- .../zwave-js/src/lib/test/cc/CRC16CC.test.ts | 43 +- .../src/lib/test/cc/CentralSceneCC.test.ts | 64 +- .../src/lib/test/cc/ColorSwitchCC.test.ts | 77 +- .../cc/CommandClass.nonImplemented.test.ts | 2 +- .../cc/CommandClass.persistValues.test.ts | 16 +- .../src/lib/test/cc/CommandClass.test.ts | 43 +- .../src/lib/test/cc/DoorLockCC.test.ts | 97 +- .../src/lib/test/cc/DoorLockLoggingCC.test.ts | 30 +- .../src/lib/test/cc/EntryControlCC.test.ts | 48 +- .../zwave-js/src/lib/test/cc/FibaroCC.test.ts | 45 +- .../lib/test/cc/HumidityControlModeCC.test.ts | 55 +- .../HumidityControlOperatingStateCC.test.ts | 17 +- .../test/cc/HumidityControlSetpointCC.test.ts | 89 +- .../src/lib/test/cc/IndicatorCC.test.ts | 43 +- .../src/lib/test/cc/LanguageCC.test.ts | 42 +- .../test/cc/ManufacturerSpecificCC.test.ts | 17 +- .../zwave-js/src/lib/test/cc/MeterCC.test.ts | 125 +- .../test/cc/MultiChannelAssociationCC.test.ts | 78 +- .../src/lib/test/cc/MultiChannelCC.test.ts | 95 +- .../src/lib/test/cc/MultiCommandCC.test.ts | 7 +- .../lib/test/cc/MultilevelSwitchCC.test.ts | 76 +- .../src/lib/test/cc/NoOperationCC.test.ts | 11 +- .../src/lib/test/cc/PowerlevelCC.test.ts | 46 +- .../src/lib/test/cc/SceneActivationCC.test.ts | 29 +- .../cc/SceneActuatorConfigurationCC.test.ts | 33 +- .../cc/SceneControllerConfigurationCC.test.ts | 77 +- .../src/lib/test/cc/SupervisionCC.test.ts | 19 +- .../lib/test/cc/ThermostatFanModeCC.test.ts | 49 +- .../lib/test/cc/ThermostatFanStateCC.test.ts | 25 +- .../zwave-js/src/lib/test/cc/TimeCC.test.ts | 38 +- .../zwave-js/src/lib/test/cc/WakeUpCC.test.ts | 22 +- .../src/lib/test/cc/ZWavePlusCC.test.ts | 7 +- ...rySensorReportAnyUseFirstSupported.test.ts | 4 +- .../invalidCallbackFunctionTypes.test.ts | 76 +- .../compat/notificationAlarmMapping.test.ts | 2 +- .../test/compat/reInterviewWakeUpNIF.test.ts | 2 +- .../test/compliance/decodeLowerS2Keys.test.ts | 52 +- .../discardInsecureCommands.test.ts | 32 +- .../encapsulationAnswerAsAsked.test.ts | 33 +- .../handleMultiCommandPayload/7e570001.jsonl | 1 + .../handleMultiCommandPayload.test.ts | 10 +- .../secureNodeSecureEndpoint.test.ts | 55 +- .../compliance/zwavePlusInfoResponse.test.ts | 2 +- .../test/driver/assemblePartialCCs.test.ts | 113 +- .../test/driver/bootloaderDetection.test.ts | 2 +- .../driver/computeNetCCPayloadSize.test.ts | 16 +- .../lib/test/driver/controllerJammed.test.ts | 50 +- .../createCCValuesUsingKnownVersion.test.ts | 4 +- .../driver/handleNonImplementedCCs.test.ts | 4 +- .../test/driver/highestSecurityClass.test.ts | 11 +- ...noreCCVersion0ForKnownSupportedCCs.test.ts | 144 +- .../multiStageResponseNoTimeout.test.ts | 61 +- .../driver/nodeAsleepBlockNonceReport.test.ts | 32 +- .../driver/nodeAsleepMessageOrder.test.ts | 18 +- .../test/driver/nodeAsleepNoReject.test.ts | 6 +- .../lib/test/driver/nodeDeadReject.test.ts | 8 +- .../driver/nodeUpdateBeforeCallback.test.ts | 2 +- .../test/driver/notificationPushNoAGI.test.ts | 4 +- .../driver/reInterviewAssumeAwake.test.ts | 8 +- .../lib/test/driver/receiveMessages.test.ts | 10 +- .../test/driver/s0AndS2Encapsulation.test.ts | 52 +- .../lib/test/driver/s0Encapsulation.test.ts | 55 +- .../driver/s0EncapsulationTwoNodes.test.ts | 55 +- .../src/lib/test/driver/s2Collisions.test.ts | 146 +- .../driver/secureAndSupervisionEncap.test.ts | 2 +- .../driver/sendDataAbortAfterTimeout.test.ts | 38 +- .../lib/test/driver/sendDataFailThrow.test.ts | 4 +- .../sendDataMissingCallbackAbort.test.ts | 70 +- .../driver/sendDataMissingResponse.test.ts | 34 +- .../setValueFailedSupervisionGet.test.ts | 8 +- .../test/driver/setValueNoSupervision.test.ts | 4 +- .../setValueSucceedAfterFailure.test.ts | 8 +- ...setValueSuccessfulSupervisionNoGet.test.ts | 4 +- .../driver/setValueSupervision255Get.test.ts | 16 +- ...ValueSupervisionSuccessMoreUpdates.test.ts | 4 +- .../driver/setValueSupervisionWorking.test.ts | 8 +- .../driver/supervisionRepeatedReport.test.ts | 2 +- .../driver/targetValueVersionUnknown.test.ts | 8 +- .../src/lib/test/driver/unknownValues.test.ts | 24 +- .../lib/test/driver/unresponsiveStick.test.ts | 24 +- packages/zwave-js/src/lib/test/messages.ts | 2 +- packages/zwave-js/src/lib/test/mocks.ts | 172 +- .../lib/test/node/Node.constructor.test.ts | 3 +- .../lib/test/node/Node.getEndpoint.test.ts | 2 +- .../lib/test/node/Node.handleCommand.test.ts | 30 +- .../legacyRefreshActuatorSensorCCs.test.ts | 32 +- .../src/lib/zniffer/CCParsingContext.ts | 104 - packages/zwave-js/src/lib/zniffer/MPDU.ts | 2 +- packages/zwave-js/src/lib/zniffer/Zniffer.ts | 99 +- packages/zwave-js/zwave-js.api.md | 262 +- server_config.js | 23 +- test/decodeMessage.ts | 22 +- yarn.lock | 4 +- 387 files changed, 34775 insertions(+), 25800 deletions(-) create mode 100644 docs/getting-started/migrating/v14.md delete mode 100644 packages/core/src/abstractions/ICommandClass.ts delete mode 100644 packages/core/src/abstractions/IZWaveEndpoint.ts delete mode 100644 packages/core/src/abstractions/IZWaveNode.ts create mode 100644 packages/core/src/traits/CommandClasses.ts create mode 100644 packages/core/src/traits/Endpoints.ts create mode 100644 packages/core/src/traits/Nodes.ts create mode 100644 packages/core/src/traits/SecurityManagers.ts create mode 100644 packages/core/src/util/compareVersions.ts create mode 100644 packages/maintenance/src/refactorCCParsing.01.ts create mode 100644 packages/maintenance/src/refactorCCParsing.02.ts create mode 100644 packages/maintenance/src/refactorCCParsing.03.ts create mode 100644 packages/maintenance/src/refactorCCParsing.04.ts create mode 100644 packages/maintenance/src/refactorMessageParsing.01.ts create mode 100644 packages/maintenance/src/refactorMessageParsing.02.ts create mode 100644 packages/maintenance/src/refactorMessageParsing.03.ts create mode 100644 packages/serial/src/index_serialapi.ts rename packages/serial/src/{ => log}/Logger.ts (98%) rename packages/serial/src/{ => log}/Logger_safe.ts (100%) delete mode 100644 packages/serial/src/message/INodeQuery.ts rename packages/serial/src/{ => message}/MessageHeaders.ts (100%) rename packages/serial/src/{ => mock}/MockSerialPort.ts (95%) rename packages/serial/src/{ => mock}/SerialPortBindingMock.ts (100%) rename packages/serial/src/{ => mock}/SerialPortMock.ts (89%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/ApplicationCommandRequest._test.ts (100%) create mode 100644 packages/serial/src/serialapi/application/ApplicationCommandRequest.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/ApplicationUpdateRequest.ts (54%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/BridgeApplicationCommandRequest.test.ts (68%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/BridgeApplicationCommandRequest.ts (51%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/SerialAPIStartedRequest.ts (68%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/application/ShutdownMessages.ts (60%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/GetControllerCapabilitiesMessages.ts (50%) create mode 100644 packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts create mode 100644 packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts create mode 100644 packages/serial/src/serialapi/capability/GetProtocolVersionMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/GetSerialApiCapabilitiesMessages.ts (53%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/GetSerialApiInitDataMessages.ts (62%) create mode 100644 packages/serial/src/serialapi/capability/HardResetRequest.ts create mode 100644 packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/SerialAPISetupMessages.test.ts (83%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/SerialAPISetupMessages.ts (56%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/SetApplicationNodeInformationRequest.ts (93%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts (54%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/memory/GetControllerIdMessages.ts (57%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/misc/GetBackgroundRSSIMessages.ts (58%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/misc/SetRFReceiveModeMessages.ts (53%) create mode 100644 packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/misc/SoftResetRequest.ts (100%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/misc/WatchdogMessages.ts (100%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/AddNodeToNetworkRequest.ts (69%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts (53%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts (51%) create mode 100644 packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts create mode 100644 packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts create mode 100644 packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts create mode 100644 packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts (100%) create mode 100644 packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/GetPriorityRouteMessages.ts (54%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/GetRoutingInfoMessages.ts (62%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/GetSUCNodeIdMessages.ts (52%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/IsFailedNodeMessages.ts (52%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/RemoveFailedNodeMessages.ts (53%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts (56%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts (54%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/RequestNodeInfoMessages.ts (61%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts (51%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/SetLearnModeMessages.ts (55%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/network-mgmt/SetPriorityRouteMessages.ts (59%) create mode 100644 packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/ExtNVMReadLongBufferMessages.ts (50%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/ExtNVMReadLongByteMessages.ts (50%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts (51%) create mode 100644 packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/ExtendedNVMOperationsMessages.ts (58%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/FirmwareUpdateNVMMessages.ts (57%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/GetNVMIdMessages.ts (73%) rename packages/{zwave-js/src/lib => serial/src}/serialapi/nvm/NVMOperationsMessages.ts (57%) create mode 100644 packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts create mode 100644 packages/serial/src/serialapi/transport/SendDataMessages.ts rename packages/{zwave-js/src/lib => serial/src}/serialapi/transport/SendDataShared.ts (90%) create mode 100644 packages/serial/src/serialapi/transport/SendTestFrameMessages.ts create mode 100644 packages/serial/src/serialapi/utils.ts rename packages/serial/src/{ => serialport}/DisconnectError.ts (100%) rename packages/serial/src/{ => serialport}/ZWaveSerialPort.test.ts (96%) rename packages/serial/src/{ => serialport}/ZWaveSerialPort.ts (100%) rename packages/serial/src/{ => serialport}/ZWaveSerialPortBase.ts (97%) rename packages/serial/src/{ => serialport}/ZWaveSerialPortImplementation.ts (100%) rename packages/serial/src/{ => serialport}/ZWaveSocket.ts (100%) rename packages/serial/src/{ => serialport}/ZWaveSocketOptions.ts (100%) rename packages/serial/src/{ => zniffer}/ZnifferSerialPort.ts (96%) rename packages/serial/src/{ => zniffer}/ZnifferSerialPortBase.ts (96%) rename packages/serial/src/{ => zniffer}/ZnifferSocket.ts (96%) create mode 100644 packages/zwave-js/src/lib/node/mixins/00_Base.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/01_NetworkRole.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/05_Security.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/10_Events.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/20_Status.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/30_Wakeup.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/40_Values.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/50_Endpoints.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/60_ScheduledPoll.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/70_FirmwareUpdate.ts create mode 100644 packages/zwave-js/src/lib/node/mixins/README.md create mode 100644 packages/zwave-js/src/lib/node/mixins/index.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/_Types.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts delete mode 100644 packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts delete mode 100644 packages/zwave-js/src/lib/zniffer/CCParsingContext.ts diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets index a6d55319776e..e90d01cb4ee6 100644 --- a/.vscode/typescript.code-snippets +++ b/.vscode/typescript.code-snippets @@ -4,24 +4,24 @@ "prefix": "zwmsg", "body": [ "import {", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveApplicationHost, ZWaveHost } from \"@zwave-js/host\";", "import {", "\tFunctionType,", - "\tgotDeserializationOptions,", "\tMessage,", - "\tMessageBaseOptions,", - "\tMessageDeserializationOptions,", + "\ttype MessageBaseOptions,", + "\ttype MessageEncodingContext,", + "\ttype MessageParsingContext,", + "\ttype MessageRaw,", "\tMessageType,", "\tmessageTypes,", "\tpriority,", "} from \"@zwave-js/serial\";", "", - "export interface ${1}RequestOptions extends MessageBaseOptions {", + "export interface ${1}RequestOptions {", "\t${0:someProperty: number;}", "}", "", @@ -29,27 +29,33 @@ "@priority(MessagePriority.${2:Normal})", "export class ${1}Request extends Message {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions | ${1}RequestOptions,", + "\t\toptions: ${1}RequestOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "\t", + "\t\t// return new this({", + "\t\t// \t// ...", + "\t\t// });", + "\t}", + "", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -68,26 +74,26 @@ "prefix": "zwmsgres", "body": [ "import {", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveApplicationHost, ZWaveHost } from \"@zwave-js/host\";", "import {", "\texpectedResponse,", "\tFunctionType,", - "\tgotDeserializationOptions,", "\tMessage,", - "\tMessageBaseOptions,", - "\tMessageDeserializationOptions,", + "\ttype MessageBaseOptions,", + "\ttype MessageEncodingContext,", + "\ttype MessageParsingContext,", + "\ttype MessageRaw,", "\tMessageType,", "\tmessageTypes,", "\tpriority,", - "\tSuccessIndicator,", + "\ttype SuccessIndicator,", "} from \"@zwave-js/serial\";", "", - "export interface ${1}RequestOptions extends MessageBaseOptions {", + "export interface ${1}RequestOptions {", "\t${0:someProperty: number;}", "}", "", @@ -96,27 +102,33 @@ "@expectedResponse(FunctionType.${1})", "export class ${1}Request extends Message {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions | ${1}RequestOptions,", + "\t\toptions: ${1}RequestOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "\t", + "\t\t// return new this({", + "\t\t// \t// ...", + "\t\t// });", + "\t}", + "", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -129,11 +141,28 @@ "\t}", "}", "", + "export interface ${1}ResponseOptions {", + "\twasSent: boolean", + "}", + "", "@messageTypes(MessageType.Response, FunctionType.${1})", "export class ${1}Response extends Message implements SuccessIndicator {", - "\tpublic constructor(host: ZWaveHost, options: MessageDeserializationOptions) {", - "\t\tsuper(host, options);", - "\t\tthis.wasSent = this.payload[0] !== 0;", + "\tpublic constructor(", + "\t\toptions: ${1}ResponseOptions & MessageBaseOptions,", + "\t) {", + "\t\tsuper(options);", + "\t\tthis.wasSent = options.wasSent;", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Response {", + "\t\tconst wasSent = raw.payload[0] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\twasSent,", + "\t\t});", "\t}", "", "\tisOK(): boolean {", @@ -156,42 +185,43 @@ "prefix": "zwmsgrescb", "body": [ "import {", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveApplicationHost, ZWaveHost } from \"@zwave-js/host\";", "import {", "\texpectedCallback,", "\texpectedResponse,", "\tFunctionType,", - "\tgotDeserializationOptions,", "\tMessage,", - "\tMessageBaseOptions,", - "\tMessageDeserializationOptions,", - "\tMessageOptions,", + "\ttype MessageBaseOptions,", + "\ttype MessageEncodingContext,", + "\tMessageOrigin,", + "\ttype MessageParsingContext,", + "\ttype MessageRaw,", "\tMessageType,", "\tmessageTypes,", "\tpriority,", - "\tSuccessIndicator,", + "\ttype SuccessIndicator,", "} from \"@zwave-js/serial\";", "", "@messageTypes(MessageType.Request, FunctionType.${1:Dummy})", "@priority(MessagePriority.${2:Normal})", "export class ${1}RequestBase extends Message {", - "\tpublic constructor(host: ZWaveHost, options: MessageOptions) {", - "\t\tif (", - "\t\t\tgotDeserializationOptions(options) &&", - "\t\t\t(new.target as any) !== ${1}Callback", - "\t\t) {", - "\t\t\treturn new ${1}Callback(host, options);", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}RequestBase {", + "\t\tif (ctx.origin === MessageOrigin.Host) {", + "\t\t\treturn ${1}Request.from(raw, ctx);", + "\t\t} else {", + "\t\t\treturn ${1}Callback.from(raw, ctx);", "\t\t}", - "\t\tsuper(host, options);", "\t}", "}", "", - "export interface ${1}RequestOptions extends MessageBaseOptions {", + "export interface ${1}RequestOptions {", "\t${0:someProperty: number;}", "}", "", @@ -199,28 +229,35 @@ "@expectedCallback(FunctionType.${1})", "export class ${1}Request extends ${1}RequestBase {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions | ${1}RequestOptions,", + "\t\toptions: ${1}RequestOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "\t", + "\t\t// return new this({", + "\t\t// \t// ...", + "\t\t// });", + "\t}", + "", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", + "\t\tthis.assertCallbackId();", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t\tthis.callbackId,", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -233,11 +270,28 @@ "\t}", "}", "", + "export interface ${1}ResponseOptions {", + "\twasSent: boolean", + "}", + "", "@messageTypes(MessageType.Response, FunctionType.${1})", "export class ${1}Response extends Message {", - "\tpublic constructor(host: ZWaveHost, options: MessageDeserializationOptions) {", - "\t\tsuper(host, options);", - "\t\tthis.wasSent = this.payload[0] !== 0;", + "\tpublic constructor(", + "\t\toptions: ${1}ResponseOptions & MessageBaseOptions,", + "\t) {", + "\t\tsuper(options);", + "\t\tthis.wasSent = options.wasSent;", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Response {", + "\t\tconst wasSent = raw.payload[0] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\twasSent,", + "\t\t});", "\t}", "", "\tpublic readonly wasSent: boolean;", @@ -250,14 +304,33 @@ "\t}", "}", "", + "export interface ${1}CallbackOptions {", + "\tsuccess: boolean", + "}", + "", "export class ${1}Callback", "\textends ${1}RequestBase", "\timplements SuccessIndicator {", - "\tpublic constructor(host: ZWaveHost, options: MessageDeserializationOptions) {", - "\t\tsuper(host, options);", + "\tpublic constructor(", + "\t\toptions: ${1}CallbackOptions & MessageBaseOptions,", + "\t) {", + "\t\tsuper(options);", + "", + "\t\tthis.callbackId = options.callbackId;", + "\t\tthis.success = options.success;", + "\t}", "", - "\t\tthis.callbackId = this.payload[0];", - "\t\tthis.success = this.payload[1] !== 0;", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tconst callbackId = raw.payload[0];", + "\t\tconst success = raw.payload[1] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\tcallbackId,", + "\t\t\tsuccess,", + "\t\t});", "\t}", "", "\tisOK(): boolean {", @@ -283,69 +356,77 @@ "prefix": "zwmsgcb", "body": [ "import {", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveApplicationHost, ZWaveHost } from \"@zwave-js/host\";", "import {", - "\tFunctionType,", - "\tMessageType,", "\texpectedCallback,", - "\tgotDeserializationOptions,", + "\tFunctionType,", "\tMessage,", - "\tMessageBaseOptions,", - "\tMessageDeserializationOptions,", - "\tMessageOptions,", + "\ttype MessageBaseOptions,", + "\ttype MessageEncodingContext,", + "\tMessageOrigin,", + "\ttype MessageParsingContext,", + "\ttype MessageRaw,", + "\tMessageType,", "\tmessageTypes,", "\tpriority,", - "\tSuccessIndicator,", + "\ttype SuccessIndicator,", "} from \"@zwave-js/serial\";", "", "@messageTypes(MessageType.Request, FunctionType.${1:Dummy})", "@priority(MessagePriority.${2:Normal})", "export class ${1}RequestBase extends Message {", - "\tpublic constructor(host: ZWaveHost, options: MessageOptions) {", - "\t\tif (", - "\t\t\tgotDeserializationOptions(options) &&", - "\t\t\t(new.target as any) !== ${1}Callback", - "\t\t) {", - "\t\t\treturn new ${1}Callback(host, options);", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}RequestBase {", + "\t\tif (ctx.origin === MessageOrigin.Host) {", + "\t\t\treturn ${1}Request.from(raw, ctx);", + "\t\t} else {", + "\t\t\treturn ${1}Callback.from(raw, ctx);", "\t\t}", - "\t\tsuper(host, options);", "\t}", "}", "", - "export interface ${1}RequestOptions extends MessageBaseOptions {", + "export interface ${1}RequestOptions {", "\t${0:someProperty: number;}", "}", "", "@expectedCallback(FunctionType.${1})", "export class ${1}Request extends ${1}RequestBase {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions | ${1}RequestOptions,", + "\t\toptions: ${1}RequestOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "\t", + "\t\t// return new this({", + "\t\t// \t// ...", + "\t\t// });", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", + "\t\tthis.assertCallbackId();", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t\tthis.callbackId,", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -358,14 +439,33 @@ "\t}", "}", "", + "export interface ${1}CallbackOptions {", + "\tsuccess: boolean", + "}", + "", "export class ${1}Callback", "\textends ${1}RequestBase", "\timplements SuccessIndicator {", - "\tpublic constructor(host: ZWaveHost, options: MessageDeserializationOptions) {", - "\t\tsuper(host, options);", + "\tpublic constructor(", + "\t\toptions: ${1}CallbackOptions & MessageBaseOptions,", + "\t) {", + "\t\tsuper(options);", "", - "\t\tthis.callbackId = this.payload[0];", - "\t\tthis.success = this.payload[1] !== 0;", + "\t\tthis.callbackId = options.callbackId;", + "\t\tthis.success = options.success;", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tconst callbackId = raw.payload[0];", + "\t\tconst success = raw.payload[1] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\tcallbackId,", + "\t\t\tsuccess,", + "\t\t});", "\t}", "", "\tisOK(): boolean {", @@ -392,26 +492,22 @@ "body": [ "import {", "\tcreateSimpleReflectionDecorator,", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", - "\tMessageRecord,", + "\ttype MessageRecord,", "\tvalidatePayload,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveHost } from \"@zwave-js/host\";", - "import type {", - "\tDeserializingMessageConstructor,", - "\tSuccessIndicator,", - "} from \"@zwave-js/serial\";", "import {", "\texpectedResponse,", "\tFunctionType,", - "\tgotDeserializationOptions,", "\tMessage,", - "\tMessageBaseOptions,", - "\tMessageDeserializationOptions,", - "\tMessageOptions,", + "\ttype MessageBaseOptions,", + "\ttype MessageConstructor,", + "\ttype MessageEncodingContext,", + "\ttype MessageParsingContext,", + "\ttype MessageRaw,", "\tMessageType,", "\tmessageTypes,", "\tpriority,", @@ -425,12 +521,12 @@ "// We need to define the decorators for Requests and Responses separately", "const {", "\tdecorator: subCommandRequest,", - "\t// lookupConstructor: getSubCommandRequestConstructor,", + "\tlookupConstructor: getSubCommandRequestConstructor,", "\tlookupValue: getSubCommandForRequest,", "} = createSimpleReflectionDecorator<", "\t${1}Request,", "\t[command: ${1}Command],", - "\tDeserializingMessageConstructor<${1}Request>", + "\tMessageConstructor<${1}Request>", ">({", "\tname: \"subCommandRequest\",", "});", @@ -438,10 +534,11 @@ "const {", "\tdecorator: subCommandResponse,", "\tlookupConstructor: getSubCommandResponseConstructor,", + "\tlookupValue: getSubCommandForResponse,", "} = createSimpleReflectionDecorator<", "\t${1}Response,", "\t[command: ${1}Command],", - "\tDeserializingMessageConstructor<${1}Response>", + "\tMessageConstructor<${1}Response>", ">({", "\tname: \"subCommandResponse\",", "});", @@ -454,31 +551,53 @@ "\treturn (sent as ${1}Request).command === received.command;", "}", "", + "export interface ${1}RequestOptions {", + "\tcommand?: ${1}Command;", + "}", + "", "@messageTypes(MessageType.Request, FunctionType.${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}})", "@priority(MessagePriority.${2:Normal})", "@expectedResponse(testResponseFor${1}Request)", "export class ${1}Request extends Message {", - "\tpublic constructor(host: ZWaveHost, options: MessageOptions = {}) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\tthis.command = getSubCommandForRequest(this)!;", + "\tpublic constructor(", + "\t\toptions: ${1}RequestOptions & MessageBaseOptions,", + "\t) {", + "\t\tsuper(options);", + "\t\tthis.command = options.command ?? getSubCommandForRequest(this)!;", + "\t}", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Request {", + "\t\tconst command: ${1}Command = raw.payload[0];", + "\t\tconst payload = raw.payload.subarray(1);", + "", + "\t\tconst CommandConstructor = getSubCommandRequestConstructor(", + "\t\t\tcommand,", + "\t\t);", + "\t\tif (CommandConstructor) {", + "\t\t\treturn CommandConstructor.from(", + "\t\t\t\traw.withPayload(payload),", + "\t\t\t\tctx,", + "\t\t\t) as ${1}Request;", "\t\t}", + "", + "\t\tconst ret = new ${1}Request({", + "\t\t\tcommand,", + "\t\t});", + "\t\tret.payload = payload;", + "\t\treturn ret;", "\t}", "", "\tpublic command: ${1}Command;", "", - "\tpublic serialize(): Buffer {", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", "\t\tthis.payload = Buffer.concat([", "\t\t\tBuffer.from([this.command]),", "\t\t\tthis.payload,", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -495,23 +614,41 @@ "\t}", "}", "", + "export interface ${1}ResponseOptions {", + "\tcommand?: ${1}Command;", + "}", + "", "@messageTypes(MessageType.Response, FunctionType.${1})", "export class ${1}Response extends Message {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions,", + "\t\toptions: ${1}ResponseOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tthis.command = this.payload[0];", + "\t\tsuper(options);", + "\t\tthis.command = options.command ?? getSubCommandForResponse(this)!;", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}Response {", + "\t\tconst command: ${1}Command = raw.payload[0];", + "\t\tconst payload = raw.payload.subarray(1);", "", "\t\tconst CommandConstructor = getSubCommandResponseConstructor(", - "\t\t\tthis.command,", + "\t\t\tcommand,", "\t\t);", - "\t\tif (CommandConstructor && (new.target as any) !== CommandConstructor) {", - "\t\t\treturn new CommandConstructor(host, options);", + "\t\tif (CommandConstructor) {", + "\t\t\treturn CommandConstructor.from(", + "\t\t\t\traw.withPayload(payload),", + "\t\t\t\tctx,", + "\t\t\t) as ${1}Response;", "\t\t}", "", - "\t\tthis.payload = this.payload.slice(1);", + "\t\tconst ret = new ${1}Response({", + "\t\t\tcommand,", + "\t\t});", + "\t\tret.payload = payload;", + "\t\treturn ret;", "\t}", "", "\tpublic command: ${1}Command;", @@ -537,38 +674,40 @@ "body": [ "// =============================================================================", "", - "export interface ${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}}_${2:SomeCommand}RequestOptions extends MessageBaseOptions {", + "export interface ${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}}_${2:SomeCommand}RequestOptions {", "\t${0:someProperty: number;}", "}", "", "@subCommandRequest(${1}Command.${2})", "export class ${1}_${2}Request extends ${1}Request {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions:", - "\t\t\t| MessageDeserializationOptions", - "\t\t\t| ${1}_${2}RequestOptions,", + "\t\toptions: ${1}_${2}RequestOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tthis.command = ${1}Command.${2};", - "", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}_${2}Request {", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "\t", + "\t\t// return new this({", + "\t\t// \t// ...", + "\t\t// });", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic serialize(ctx: MessageEncodingContext): Buffer {", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t]);", "", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -580,17 +719,32 @@ "\t}", "}", "", + "export interface ${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}}_${2:SomeCommand}ResponseOptions {", + "\twasSent: boolean;", + "}", + "", "@subCommandResponse(${1}Command.${2})", "export class ${1}_${2}Response extends ${1}Response", "{", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions,", + "\t\toptions: ${1}_${2}ResponseOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tthis.wasSent = this.payload[0] !== 0;", + "\t\tsuper(options);", + "\t\tthis.wasSent = options.wasSent;", + "\t}", + "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}_${2}Response {", + "\t\tconst wasSent = raw.payload[0] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\twasSent,", + "\t\t});", "\t}", "", + "", "\tpublic readonly wasSent: boolean;", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -612,17 +766,32 @@ "@subCommandRequest(${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}}Command.${2:SomeCommand})", "export class ${1}_${2}Request extends ${1}Request {}", "", + "export interface ${1:${TM_FILENAME_BASE/(.*)Messages$/$1/}}_${2:SomeCommand}ResponseOptions {", + "\twasSent: boolean;", + "}", + "", "@subCommandResponse(${1}Command.${2})", "export class ${1}_${2}Response extends ${1}Response", "{", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: MessageDeserializationOptions,", + "\t\toptions: ${1}_${2}ResponseOptions & MessageBaseOptions,", "\t) {", - "\t\tsuper(host, options);", - "\t\tthis.wasSent = this.payload[0] !== 0;", + "\t\tsuper(options);", + "\t\tthis.wasSent = options.wasSent;", "\t}", "", + "\tpublic static from(", + "\t\traw: MessageRaw,", + "\t\tctx: MessageParsingContext,", + "\t): ${1}_${2}Response {", + "\t\tconst wasSent = raw.payload[0] !== 0;", + "\t", + "\t\treturn new this({", + "\t\t\twasSent,", + "\t\t});", + "\t}", + "", + "", "\tpublic readonly wasSent: boolean;", "", "\tpublic toLogEntry(): MessageOrCCLogEntry {", @@ -639,9 +808,9 @@ "scope": "typescript", "prefix": "zwcclog", "body": [ - "public toLogEntry(applHost: ZWaveApplicationHost): MessageOrCCLogEntry {", + "public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry {", "\treturn {", - "\t\t...super.toLogEntry(applHost),", + "\t\t...super.toLogEntry(ctx),", "\t\tmessage: { ${1:someProp}: this.${1} },", "\t};", "}" @@ -651,9 +820,9 @@ "scope": "typescript", "prefix": "zwcclogempty", "body": [ - "public toLogEntry(applHost: ZWaveApplicationHost): MessageOrCCLogEntry {", + "public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry {", "\treturn {", - "\t\t...super.toLogEntry(applHost),", + "\t\t...super.toLogEntry(ctx),", "\t\t// Hide the default payload line", "\t\tmessage: undefined,", "\t};", @@ -664,9 +833,9 @@ "scope": "typescript", "prefix": "zwccpv", "body": [ - "public persistValues(applHost: ZWaveApplicationHost): boolean {", - "\tif (!super.persistValues(applHost)) return false;", - "\tconst valueDB = this.getValueDB(applHost);", + "public persistValues(ctx: PersistValuesContext): boolean {", + "\tif (!super.persistValues(ctx)) return false;", + "\tconst valueDB = this.getValueDB(ctx);", "", "\t${0://TODO: Implementation}", "", @@ -679,28 +848,35 @@ "prefix": "zwcc", "body": [ "import {", + "\ttype CCRaw,", "\tCommandClass,", - "\tgotDeserializationOptions,", - "\ttype CCCommandOptions,", - "\ttype CommandClassDeserializationOptions,", + "\ttype InterviewContext,", + "\ttype PersistValuesContext,", + "\ttype RefreshValuesContext,", "} from \"../lib/CommandClass\";", "import {", + "\tAPI,", "\tCCCommand,", "\tccValue,", "\tccValues,", "\tcommandClass,", "\texpectedCCResponse,", "\timplementedVersion,", + "\tuseSupervision,", "} from \"../lib/CommandClassDecorators\";", "import {", "\tCommandClasses,", - "\tMessageOrCCLogEntry,", + "\ttype MessageOrCCLogEntry,", "\tMessagePriority,", + "\tValueMetadata,", + "\ttype WithAddress,", + "\tvalidatePayload,", "\tZWaveError,", "\tZWaveErrorCodes,", "} from \"@zwave-js/core\";", - "import type { ZWaveApplicationHost, ZWaveHost } from \"@zwave-js/host\";", + "import { CCEncodingContext, CCParsingContext } from \"@zwave-js/host\";", "import { CCAPI } from \"../lib/API\";", + "import { V } from \"../lib/Values\";", "", "// TODO: Move this enumeration into the src/lib/_Types.ts file", "// All additional type definitions (except CC constructor options) must be defined there too", @@ -808,7 +984,8 @@ "scope": "typescript", "prefix": "zwcccmd", "body": [ - "interface ${1:${TM_FILENAME_BASE/(.*)CC$/$1/}}CC${2:Get}Options extends CCCommandOptions {", + "// @publicAPI", + "export interface ${1:${TM_FILENAME_BASE/(.*)CC$/$1/}}CC${2:Get}Options {", "\tsomeProperty: number;", "}", "", @@ -816,27 +993,30 @@ "@expectedCCResponse(${1}CCReport)", "export class ${1}CC${2} extends ${1}CC {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: CommandClassDeserializationOptions | ${1}CC${2}Options,", + "\t\toptions: WithAddress<${1}CC${2}Options>,", "\t) {", - "\t\tsuper(host, options);", - "\t\tif (gotDeserializationOptions(options)) {", - "\t\t\t// TODO: Deserialize payload", - "\t\t\tthrow new ZWaveError(", - "\t\t\t\t`${this.constructor.name}: deserialization not implemented`,", - "\t\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", - "\t\t\t);", - "\t\t} else {", - "\t\t\t// TODO: Populate properties from options object", - "\t\t\tthrow new Error(\"not implemented\");", - "\t\t}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", + "\t}", + "\tpublic static from(raw: CCRaw, ctx: CCParsingContext): ${1}CC${2} {", + "\t\t// TODO: Deserialize payload", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "", + "\t\t// return new ${1}CC${2}({", + "\t\t// \tnodeId: ctx.sourceNodeId,", + "\t\t// \t// ...", + "\t\t// });", "\t}", "", - "\tpublic serialize(): Buffer {", + "\tpublic serialize(ctx: CCEncodingContext): Buffer {", "\t\tthis.payload = Buffer.from([", "\t\t\t/* TODO: serialize */", "\t\t]);", - "\t\treturn super.serialize();", + "\t\treturn super.serialize(ctx);", "\t}", "}", "${0}" @@ -847,14 +1027,32 @@ "scope": "typescript", "prefix": "zwccreport", "body": [ - "@CCCommand(${1:${TM_FILENAME_BASE/(.*)CC$/$1/}}Command.${2}Report)", - "export class ${1}CC${2}Report extends ${1}CC {", + "// @publicAPI", + "export interface ${1:${TM_FILENAME_BASE/(.*)CC$/$1/}}CC${2:Report}Options {", + "\tsomeProperty: number;", + "}", + "", + "@CCCommand(${1:${TM_FILENAME_BASE/(.*)CC$/$1/}}Command.${2})", + "export class ${1}CC${2} extends ${1}CC {", "\tpublic constructor(", - "\t\thost: ZWaveHost,", - "\t\toptions: CommandClassDeserializationOptions,", + "\t\toptions: WithAddress<${1}CC${2}Options>,", "\t) {", - "\t\tsuper(host, options);", - "\t\t${0:// TODO: Deserialize}", + "\t\tsuper(options);", + "\t\t// TODO: Populate properties from options object", + "\t\tthrow new Error(\"not implemented\");", + "\t}", + "", + "\tpublic static from(raw: CCRaw, ctx: CCParsingContext): ${1}CC${2} {", + "\t\t// TODO: Deserialize payload", + "\t\tthrow new ZWaveError(", + "\t\t\t`${this.name}: deserialization not implemented`,", + "\t\t\tZWaveErrorCodes.Deserialization_NotImplemented,", + "\t\t);", + "", + "\t\t// return new ${1}CC${2}({", + "\t\t// \tnodeId: ctx.sourceNodeId,", + "\t\t// \t// ...", + "\t\t// });", "\t}", "}" ], @@ -951,39 +1149,38 @@ "scope": "typescript", "prefix": "zwccinterview", "body": [ - "public async interview(applHost: ZWaveApplicationHost): Promise {", - "\tconst node = this.getNode(applHost)!;", - "\tconst endpoint = this.getEndpoint(applHost)!;", + "public async interview(ctx: InterviewContext): Promise {", + "\tconst node = this.getNode(ctx)!;", + "\tconst endpoint = this.getEndpoint(ctx)!;", "\tconst api = CCAPI.create(", "\t\tCommandClasses.${1:${TM_FILENAME_BASE/(.*)CC$/$1/}},", - "\t\tapplHost,", + "\t\tctx,", "\t\tendpoint", "\t).withOptions({", "\t\tpriority: MessagePriority.NodeQuery,", "\t});", - "\tconst valueDB = this.getValueDB(applHost);", "", - "\tapplHost.controllerLog.logNode(node.id, {", + "\tctx.logNode(node.id, {", "\t\tendpoint: this.endpointIndex,", "\t\tmessage: `Interviewing ${this.ccName}...`,", "\t\tdirection: \"none\",", "\t});", "", - "\tapplHost.controllerLog.logNode(node.id, {", + "\tctx.logNode(node.id, {", "\t\tendpoint: this.endpointIndex,", "\t\tmessage: \"doing something...\",", "\t\tdirection: \"outbound\",", "\t});", "\t${0:// TODO: Implementation}", "\tconst logMessage = `received response for something...`;", - "\tapplHost.controllerLog.logNode(node.id, {", + "\tctx.logNode(node.id, {", "\t\tendpoint: this.endpointIndex,", "\t\tmessage: logMessage,", "\t\tdirection: \"inbound\",", "\t});", "", "\t// Remember that the interview is complete", - "\tthis.setInterviewComplete(applHost, true);", + "\tthis.setInterviewComplete(ctx, true);", "}" ] }, @@ -1003,17 +1200,16 @@ "scope": "typescript", "prefix": "zwccrefval", "body": [ - "public async refreshValues(applHost: ZWaveApplicationHost): Promise {", - "\tconst node = this.getNode(applHost)!;", - "\tconst endpoint = this.getEndpoint(applHost)!;", + "public async refreshValues(ctx: RefreshValuesContext): Promise {", + "\tconst node = this.getNode(ctx)!;", + "\tconst endpoint = this.getEndpoint(ctx)!;", "\tconst api = CCAPI.create(", "\t\tCommandClasses.${1:${TM_FILENAME_BASE/(.*)CC$/$1/}},", - "\t\tapplHost,", + "\t\tctx,", "\t\tendpoint", "\t).withOptions({", "\t\tpriority: MessagePriority.NodeQuery,", "\t});", - "\tconst valueDB = this.getValueDB(applHost);", "", "\t${0:// TODO: Implementation}", "}" 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/docs/getting-started/migrating/README.md b/docs/getting-started/migrating/README.md index 6a241f47116f..234ac31ba905 100644 --- a/docs/getting-started/migrating/README.md +++ b/docs/getting-started/migrating/README.md @@ -1,5 +1,6 @@ # Migrating from previous versions +- [Migrating to v14](getting-started/migrating/v14.md) - [Migrating to v13](getting-started/migrating/v13.md) - [Migrating to v12](getting-started/migrating/v12.md) - [Migrating to v11](getting-started/migrating/v11.md) diff --git a/docs/getting-started/migrating/_sidebar.md b/docs/getting-started/migrating/_sidebar.md index ecb4a25f56c1..da4781281bd0 100644 --- a/docs/getting-started/migrating/_sidebar.md +++ b/docs/getting-started/migrating/_sidebar.md @@ -8,6 +8,7 @@ - [Security S2](getting-started/security-s2.md) - [Z-Wave Long Range](getting-started/long-range.md) - [Migrating from previous versions](getting-started/migrating/) + - [Migrating to v14](getting-started/migrating/v14.md) - [Migrating to v13](getting-started/migrating/v13.md) - [Migrating to v12](getting-started/migrating/v12.md) - [Migrating to v11](getting-started/migrating/v11.md) diff --git a/docs/getting-started/migrating/v14.md b/docs/getting-started/migrating/v14.md new file mode 100644 index 000000000000..a07615b64fbe --- /dev/null +++ b/docs/getting-started/migrating/v14.md @@ -0,0 +1,195 @@ +# Migrating to v14 + +This version became necessary more or less by accident. I originally planned to split some of the files that have grown too large (like `Node.ts`) into multiple smaller chunks. During this refactor, I noticed that several methods depended on a full-blown driver instance (or an appropriate abstraction), when all that was needed was access to the value DB for example. This problem appeared throughout the entire codebase, so I decided to do something about it. + +The `ZWaveHost` and `ZWaveApplicationHost` interfaces have been replaced by multiple smaller interfaces, each defining a specific subset of the functionality. All the code that previously expected to be passed one of those interfaces has been update to just accept what it actually needs. The `Driver` class still implements all of this functionality. + +Furthermore, `Message` and `CommandClass` implementations are no longer bound to a specific host instance. Instead, their methods that need access to host functionality (like value DBs, home ID, device configuration, etc.) now receive a method-specific context object. Parsing of those instances no longer happens in the constructor, but in a separate `from` method. + +All in all, this release contains a huge list of breaking changes, but most of those should not affect any application, unless very low-level APIs are frequently used. + +## Decoupled Serial API messages from host instances, split constructors and parsing + +`Message` instances no longer store a reference to their host. They are now "just data". Therefore, message constructors no longer take an instance of `ZWaveHost` as the first parameter. All needed information is passed to the relevant methods as context arguments. + +**Old: constructor for creation and parsing** + +```ts +public constructor( + options: + | MessageDeserializationOptions + | MyMessageOptions, +) { + super(options); + + if (gotDeserializationOptions(options)) { + // deserialize message from this.payload + } else { + // populate message properties from options + } +} +``` + +**New: constructor for creation, separate method for parsing** + +```ts +public constructor( + options: MyMessageOptions & MessageBaseOptions, +) { + super(options); + // populate message properties from options +} + +public static from( + raw: MessageRaw, + ctx: MessageParsingContext, +): MyMessage { + // deserialize message from raw.payload + + return new this({ + // ... + }); +} +``` + +> [!NOTE]: For messages that contain a serialized CC instance, Message.parse no longer deserializes it automatically. This has to be done in a separate step. + +## Decoupled CCs from host instances, split constructors and parsing + +`CommandClass` instances no longer store a reference to their host. They are now "just data". Therefore, CC constructors no longer take an instance of `ZWaveHost` as the first parameter. All needed information is passed to the relevant methods as context arguments. + +**Old: constructor for creation and parsing** + +```ts +public constructor( + host: ZWaveHost, + options: CommandClassDeserializationOptions | BinarySwitchCCSetOptions, +) { + super(host, options); + if (gotDeserializationOptions(options)) { + validatePayload(this.payload.length >= 1); + this.targetValue = !!this.payload[0]; + if (this.payload.length >= 2) { + this.duration = Duration.parseSet(this.payload[1]); + } + } else { + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } +} +``` + +**New: constructor for creation, separate method for parsing** + +```ts +public constructor( + options: WithAddress, +) { + super(options); + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); +} + +public static from(raw: CCRaw, ctx: CCParsingContext): BinarySwitchCCSet { + validatePayload(raw.payload.length >= 1); + const targetValue = !!raw.payload[0]; + + let duration: Duration | undefined; + if (raw.payload.length >= 2) { + duration = Duration.parseSet(raw.payload[1]); + } + + return new BinarySwitchCCSet({ + nodeId: ctx.sourceNodeId, + targetValue, + duration, + }); +} +``` + +**Updated CC method signatures:** + +```diff +- interview(applHost: ZWaveApplicationHost): Promise; ++ interview(ctx: InterviewContext): Promise; + +- refreshValues(applHost: ZWaveApplicationHost): Promise; ++ refreshValues(ctx: RefreshValuesContext): Promise; + +- persistValues(applHost: ZWaveApplicationHost): Promise; ++ persistValues(ctx: PersistValuesContext): Promise; + +- toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; ++ toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; +``` + +> [!NOTE]: Applications calling these methods can simply pass the driver instance. + +## Moved Serial API message implementations to `@zwave-js/serial` package + +All serial API message implementations have been moved to the `@zwave-js/serial` package. In addition to the main entry point, they are also available via `@zwave-js/serial/serialapi`. + +## Other notable changes + +These are the only ones I found to break dependent projects. + +- All `getNodeUnsafe` methods have been renamed to `tryGetNode` to better indicate what they do - it was not really clear what was "unsafe" about them. +- The `CommandClass.version` property has been removed. To determine the CC version a node or endpoint supports, use the `node.getCCVersion(cc)` or `endpoint.getCCVersion(cc)` methods. +- The `TXReport` object no longer has the `numRepeaters` property, as this was implied by the length of the `repeaterNodeIds` array. + +## Other breaking changes + +Most of these should not affect any application: + +- The `ZWaveHost` and `ZWaveApplicationHost` interfaces have been split into multiple smaller "traits" +- The `Driver` class still implements all those traits, but receiving functions now just require the necessary subset. +- CC Constructors no longer take an instance of `ZWaveHost` as the first parameter. All needed information has been merged into the `CCParsingContext` type. +- The `CommandClass.from` method no longer takes an instance of `ZWaveHost` as the first parameter for the same reason. +- `CommandClass` instances no longer store a reference to their host. They are now "just data". +- The `serialize()` implementations of CCs now take an argument of type `CCEncodingContext` +- The `toLogEntry()` implementations of CCs now take an argument of type `GetValueDB` instead of `ZWaveHost` +- The `interview()` implementations of CCs now take an argument of type `InterviewContext` instead of `ZWaveApplicationHost` +- The `refreshValues()` implementations of CCs now take an argument of type `RefreshValuesContext` instead of `ZWaveApplicationHost` +- The `persistValues()` implementations of CCs now take an argument of type `PersistValuesContext` instead of `ZWaveApplicationHost` +- The `translateProperty()` and `translatePropertyKey()` implementations of CCs now take an argument of type `GetValueDB` instead of `ZWaveApplicationHost` +- The `mergePartialCCs` signature has changed: + ```diff + - mergePartialCCs(applHost: ZWaveApplicationHost, partials: CommandClass[]): void; + + mergePartialCCs(partials: CommandClass[], ctx: CCParsingContext): void; + ``` +- The `ICommandClass` interface has been removed. Where applicable, it has been replaced with the `CCId` interface whose sole purpose is to identify the command and destination of a CC. +- The `IZWaveNode`, `IZWaveEndpoint`, `IVirtualNode` and `IVirtualEndpoint` interfaces have been replaced with more specific "trait" interfaces. +- The `TestingHost`, `TestNode` and `TestEndpoint` interfaces and implementations have been reworked to be more declarative and require less input in tests. +- Serial API `Message` constructors no longer take an instance of `ZWaveHost` as the first parameter. All needed information has been merged into the `MessageParsingContext` type. +- The `Message.from` method no longer takes an instance of `ZWaveHost` as the first parameter for the same reason. +- The `callbackId` of a `Message` instance is no longer automatically generated when reading it for the first time. Instead, the `callbackId` can be `undefined` and has to be explicitly set before serializing if necessary. +- The `serialize()` implementations of `Message`s now take an argument of type `MessageEncodingContext` +- The `Message.getNodeUnsafe()` method has been renamed to `Message.tryGetNode` +- The `Driver.getNodeUnsafe(message)` method has been renamed to `Driver.tryGetNode(message)` +- `Driver.controllerLog` is no longer public. +- `Driver.getSafeCCVersion` can now return `undefined` if the requested CC is not implemented in `zwave-js` +- `Driver.isControllerNode(nodeId)` has been removed. Compare with `ownNodeId` instead or use the `Node.isControllerNode` property. +- The `Endpoint.getNodeUnsafe()` method has been renamed to `tryGetNode` +- The `[CCName]CC[CCCommand]Options` interfaces no longer extend another interface and now exclusively contain the CC-specific properties required for constructing that particular CC +- CC-specific constructors now accept a single options object of type `WithAddress<...>`, which is the CC-specific options interface, extended by `nodeId` and `endpointIndex`. Note that `endpointIndex` was previously just called `endpoint`. +- The `CommandClassDeserializationOptions` interface and the `gotDeserializationOptions` method no longer exist. Instead, CCs parse their specific payload using the new static `from` method, which takes a pre-parsed `CCRaw` (ccId, ccCommand, binary payload) and the `CCParsingContext`. All CCs with a custom constructor implementations are expected to implement this aswell. + To parse an arbitrary CC buffer, use `CommandClass.parse(buffer, context)`. +- Several CCs had their constructor options type reworked slightly for correctness. In some cases it is now necessary to pass properties that were previously inferred. +- The `CommandClassCreationOptions` type has been merged into the `CommandClassOptions` type, which now consists only of the CC destination and optional overrides for ccId, ccCommand and payload. +- `CommandClass.deserialize` has been removed. It's functionality is now provided by the `parse` method and `CCRaw`. +- `CommandClass.getCommandClass`, `CommandClass.getCCCommand` have been removed. Their functionality is now provided by `CCRaw.parse` +- `CommandClass.getConstructor` has been removed. If really needed, the functionality is available via the `getCCConstructor` and `getCCCommandConstructor` methods exported by `@zwave-js/cc`. +- The `origin` property of the `CommandClass` class was removed, since it served no real purpose. +- The `[MessageName]Options` interfaces no longer extend another interface and now exclusively contain the message-specific properties required for constructing that particular instance +- Message-specific constructors now accept a single options object of type `[MessageName]Options & MessageBaseOptions`. +- The `MessageDeserializationOptions` interface and the `gotDeserializationOptions` method no longer exist +- The `DeserializingMessageConstructor` interface no longer exists +- Instead, message instances parse their specific payload using the new static `from` method, which takes a pre-parsed `MessageRaw` (message type, function type, binary payload) and the `MessageParsingContext`. All Message implementations with a custom constructor are expected to implement the `from` method aswell. + To parse an arbitrary message buffer use `Message.parse(buffer, context)`. +- The `MessageCreationOptions` type has been renamed to `MessageOptions` +- Several Message implementations had their constructor options type reworked slightly for correctness. In some cases it is now necessary to pass properties that were previously inferred. +- The static `Message` methods `extractPayload`, `getConstructor`, `getMessageLength` and `isComplete` were removed. +- The `Message.options` property was removed +- The `ICommandClassContainer` interface was replaced with the new `ContainsCC` interface, which indicates that something contains a deserialized CC instance. +- The `Driver.computeNetCCPayloadSize` method now requires that the passed message instance contains a deserialized CC instance. +- The `INodeQuery` interface and the `isNodeQuery` function were removed. To test whether a message references a node, use `hasNodeId(msg)` instead, which communicates the intent more clearly. diff --git a/docs/usage/custom.md b/docs/usage/custom.md index 396290762d7a..4064dfbfa88d 100644 --- a/docs/usage/custom.md +++ b/docs/usage/custom.md @@ -8,7 +8,7 @@ The only downside is that you won't necessarily receive any responses that would Here's an example how to turn off the LED on a **Aeotec Gen 5 stick**. ```ts -const turnLEDOff = new Message(driver, { +const turnLEDOff = new Message({ type: MessageType.Request, functionType: 0xf2, payload: Buffer.from("5101000501", "hex"), @@ -28,7 +28,7 @@ await driver.sendMessage(turnLEDOff, { This example sends an `Anti-theft Get` command (which is currently unsupported) to node 2: ```ts -const cc = new CommandClass(driver, { +const cc = new CommandClass({ nodeId: 2, ccId: CommandClasses["Anti-theft"], // or 0x5d ccCommand: 0x02, diff --git a/packages/cc/cc.api.md b/packages/cc/cc.api.md index 8e862e9d13f3..d2d582243306 100644 --- a/packages/cc/cc.api.md +++ b/packages/cc/cc.api.md @@ -17,6 +17,12 @@ import { BatteryCCAPI } from '../cc/BatteryCC'; import { BinarySensorCCAPI } from '../cc/BinarySensorCC'; import { BinarySwitchCCAPI } from '../cc/BinarySwitchCC'; import { BroadcastCC } from '@zwave-js/core'; +import { CCAddress } from '@zwave-js/core'; +import type { CCEncodingContext } from '@zwave-js/host'; +import type { CCEncodingContext as CCEncodingContext_2 } from '@zwave-js/host/safe'; +import { CCId } from '@zwave-js/core'; +import { CCParsingContext } from '@zwave-js/host'; +import type { CCParsingContext as CCParsingContext_2 } from '@zwave-js/host/safe'; import { CentralSceneCCAPI } from '../cc/CentralSceneCC'; import { ClimateControlScheduleCCAPI } from '../cc/ClimateControlScheduleCC'; import { ClockCCAPI } from '../cc/ClockCC'; @@ -27,6 +33,8 @@ import { ConfigurationCCAPI } from '../cc/ConfigurationCC'; import { ConfigurationMetadata } from '@zwave-js/core/safe'; import { ConfigValue } from '@zwave-js/core/safe'; import { ConfigValueFormat } from '@zwave-js/core/safe'; +import { ControlsCC } from '@zwave-js/core/safe'; +import { ControlsCC as ControlsCC_2 } from '@zwave-js/core'; import { CRC16CCAPI } from '../cc/CRC16CC'; import { DataRate } from '@zwave-js/core'; import { DataRate as DataRate_2 } from '@zwave-js/core/safe'; @@ -36,40 +44,55 @@ import { DoorLockLoggingCCAPI } from '../cc/DoorLockLoggingCC'; import { Duration } from '@zwave-js/core/safe'; import { Duration as Duration_2 } from '@zwave-js/core'; import { EncapsulationFlags } from '@zwave-js/core'; +import { EndpointId } from '@zwave-js/core'; +import { EndpointId as EndpointId_2 } from '@zwave-js/core/safe'; import { EnergyProductionCCAPI } from '../cc/EnergyProductionCC'; import { EntryControlCCAPI } from '../cc/EntryControlCC'; import { FirmwareUpdateMetaDataCCAPI } from '../cc/FirmwareUpdateMetaDataCC'; import { FLiRS } from '@zwave-js/core'; import { FLiRS as FLiRS_2 } from '@zwave-js/core/safe'; import { FrameType } from '@zwave-js/core'; +import { GetAllEndpoints } from '@zwave-js/core/safe'; +import { GetAllEndpoints as GetAllEndpoints_2 } from '@zwave-js/core'; +import { GetCCs } from '@zwave-js/core'; +import type { GetCommunicationTimeouts } from '@zwave-js/host'; +import { GetDeviceConfig } from '@zwave-js/host'; +import { GetDeviceConfig as GetDeviceConfig_2 } from '@zwave-js/host/safe'; +import { GetEndpoint } from '@zwave-js/core/safe'; +import { GetEndpoint as GetEndpoint_2 } from '@zwave-js/core'; +import type { GetInterviewOptions } from '@zwave-js/host'; +import { GetNode } from '@zwave-js/host'; +import type { GetNode as GetNode_2 } from '@zwave-js/host/safe'; +import type { GetSafeCCVersion } from '@zwave-js/host'; +import type { GetSupportedCCVersion } from '@zwave-js/host'; +import type { GetSupportedCCVersion as GetSupportedCCVersion_2 } from '@zwave-js/host/safe'; +import type { GetUserPreferences } from '@zwave-js/host'; +import { GetValueDB } from '@zwave-js/host'; +import type { GetValueDB as GetValueDB_2 } from '@zwave-js/host/safe'; +import { HostIDs } from '@zwave-js/host'; import { HumidityControlModeCCAPI } from '../cc/HumidityControlModeCC'; import { HumidityControlOperatingStateCCAPI } from '../cc/HumidityControlOperatingStateCC'; import { HumidityControlSetpointCCAPI } from '../cc/HumidityControlSetpointCC'; -import { ICommandClass } from '@zwave-js/core'; import { InclusionControllerCCAPI } from '../cc/InclusionControllerCC'; import { IndicatorCCAPI } from '../cc/IndicatorCC'; import { IrrigationCCAPI } from '../cc/IrrigationCC'; -import { IVirtualEndpoint } from '@zwave-js/core'; -import { IVirtualEndpoint as IVirtualEndpoint_2 } from '@zwave-js/core/safe'; -import { IZWaveEndpoint } from '@zwave-js/core'; -import { IZWaveEndpoint as IZWaveEndpoint_2 } from '@zwave-js/core/safe'; -import { IZWaveNode } from '@zwave-js/core/safe'; -import { IZWaveNode as IZWaveNode_2 } from '@zwave-js/core'; import { JSONObject } from '@zwave-js/shared'; import { LanguageCCAPI } from '../cc/LanguageCC'; +import { ListenBehavior } from '@zwave-js/core'; import { LockCCAPI } from '../cc/LockCC'; +import type { LogNode } from '@zwave-js/host'; +import type { LookupManufacturer } from '@zwave-js/host'; import { ManufacturerProprietaryCCAPI } from '../cc/ManufacturerProprietaryCC'; import { ManufacturerSpecificCCAPI } from '../cc/ManufacturerSpecificCC'; import { MaybeNotKnown } from '@zwave-js/core/safe'; import { MaybeNotKnown as MaybeNotKnown_2 } from '@zwave-js/core'; import { MaybeUnknown } from '@zwave-js/core/safe'; import { MaybeUnknown as MaybeUnknown_2 } from '@zwave-js/core'; -import type { Message } from '@zwave-js/serial'; import { MessageOrCCLogEntry } from '@zwave-js/core'; import { MessageOrCCLogEntry as MessageOrCCLogEntry_2 } from '@zwave-js/core/safe'; -import { MessageOrigin } from '@zwave-js/serial'; import { MeterCCAPI } from '../cc/MeterCC'; import { MeterScale } from '@zwave-js/core/safe'; +import { ModifyCCs } from '@zwave-js/core'; import { MulticastCC } from '@zwave-js/core'; import { MulticastDestination } from '@zwave-js/core'; import { MultiChannelAssociationCCAPI } from '../cc/MultiChannelAssociationCC'; @@ -79,6 +102,8 @@ import { MultilevelSensorCCAPI } from '../cc/MultilevelSensorCC'; import { MultilevelSwitchCCAPI } from '../cc/MultilevelSwitchCC'; import { NODE_ID_BROADCAST } from '@zwave-js/core'; import { NODE_ID_BROADCAST_LR } from '@zwave-js/core'; +import { NodeId } from '@zwave-js/core/safe'; +import { NodeId as NodeId_2 } from '@zwave-js/core'; import { NodeInformationFrame } from '@zwave-js/core'; import { NodeNamingAndLocationCCAPI } from '../cc/NodeNamingCC'; import { NodeProtocolInfoAndDeviceClass } from '@zwave-js/core'; @@ -87,20 +112,29 @@ import { NoOperationCCAPI } from '../cc/NoOperationCC'; import { NotificationCCAPI } from '../cc/NotificationCC'; import { OnlyMethods } from '@zwave-js/shared'; import type { ParamInfoMap } from '@zwave-js/config'; +import { PhysicalNodes } from '@zwave-js/core'; import { PowerlevelCCAPI } from '../cc/PowerlevelCC'; import { ProtectionCCAPI } from '../cc/ProtectionCC'; import { ProtocolVersion } from '@zwave-js/core'; +import { QueryNodeStatus } from '@zwave-js/core'; +import { QuerySecurityClasses } from '@zwave-js/core/safe'; +import { QuerySecurityClasses as QuerySecurityClasses_2 } from '@zwave-js/core'; import { ReadonlyObjectKeyMap } from '@zwave-js/shared/safe'; import { Scale } from '@zwave-js/core/safe'; import { SceneActivationCCAPI } from '../cc/SceneActivationCC'; import { SceneActuatorConfigurationCCAPI } from '../cc/SceneActuatorConfigurationCC'; import { SceneControllerConfigurationCCAPI } from '../cc/SceneControllerConfigurationCC'; import { ScheduleEntryLockCCAPI } from '../cc/ScheduleEntryLockCC'; +import type { SchedulePoll } from '@zwave-js/host'; import { Security2CCAPI } from '../cc/Security2CC'; import { SecurityCCAPI } from '../cc/SecurityCC'; import { SecurityClass } from '@zwave-js/core'; import { SecurityManager } from '@zwave-js/core'; +import { SecurityManagers } from '@zwave-js/core'; +import { SecurityManagers as SecurityManagers_2 } from '@zwave-js/core/safe'; +import type { SendCommand } from '@zwave-js/host'; import { SendCommandOptions } from '@zwave-js/core'; +import { SetSecurityClass } from '@zwave-js/core'; import { SinglecastCC } from '@zwave-js/core'; import { SinglecastCC as SinglecastCC_2 } from '@zwave-js/core/safe'; import { SoundSwitchCCAPI } from '../cc/SoundSwitchCC'; @@ -108,6 +142,8 @@ import { SupervisionCCAPI } from '../cc/SupervisionCC'; import { SupervisionResult } from '@zwave-js/core/safe'; import { SupervisionResult as SupervisionResult_2 } from '@zwave-js/core'; import { SupervisionStatus } from '@zwave-js/core/safe'; +import { SupportsCC } from '@zwave-js/core/safe'; +import { SupportsCC as SupportsCC_2 } from '@zwave-js/core'; import { ThermostatFanModeCCAPI } from '../cc/ThermostatFanModeCC'; import { ThermostatFanStateCCAPI } from '../cc/ThermostatFanStateCC'; import { ThermostatModeCCAPI } from '../cc/ThermostatModeCC'; @@ -129,24 +165,21 @@ import { ValueID as ValueID_2 } from '@zwave-js/core/safe'; import { ValueMetadata } from '@zwave-js/core'; import { ValueMetadata as ValueMetadata_2 } from '@zwave-js/core/safe'; import { VersionCCAPI } from '../cc/VersionCC'; +import { VirtualEndpointId } from '@zwave-js/core'; import { WakeUpCCAPI } from '../cc/WakeUpCC'; import { WindowCoveringCCAPI } from '../cc/WindowCoveringCC'; -import type { ZWaveApplicationHost } from '@zwave-js/host'; -import type { ZWaveApplicationHost as ZWaveApplicationHost_2 } from '@zwave-js/host/safe'; +import { WithAddress } from '@zwave-js/core/safe'; +import { WithAddress as WithAddress_2 } from '@zwave-js/core'; import { ZWaveDataRate } from '@zwave-js/core'; import { ZWaveDataRate as ZWaveDataRate_2 } from '@zwave-js/core/safe'; import { ZWaveErrorCodes } from '@zwave-js/core'; -import type { ZWaveHost } from '@zwave-js/host'; -import type { ZWaveHost as ZWaveHost_2 } from '@zwave-js/host/safe'; import { ZWaveLibraryTypes } from '@zwave-js/core/safe'; import { ZWavePlusCCAPI } from '../cc/ZWavePlusCC'; -import type { ZWaveValueHost } from '@zwave-js/host'; -import type { ZWaveValueHost as ZWaveValueHost_2 } from '@zwave-js/host/safe'; // Warning: (ae-missing-release-tag) "addAssociations" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function addAssociations(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, group: number, destinations: AssociationAddress[]): Promise; +function addAssociations(ctx: CCAPIHost & QuerySecurityClasses>, endpoint: EndpointId_2 & SupportsCC & ControlsCC, group: number, destinations: AssociationAddress[]): Promise; // Warning: (ae-missing-release-tag) "AlarmSensorCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -155,31 +188,33 @@ export class AlarmSensorCC extends CommandClass { // (undocumented) ccCommand: AlarmSensorCommand; // (undocumented) - protected createMetadataForSensorType(applHost: ZWaveApplicationHost_2, sensorType: AlarmSensorType): void; - static getSupportedSensorTypesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; + protected createMetadataForSensorType(ctx: GetValueDB_2, sensorType: AlarmSensorType): void; + static getSupportedSensorTypesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "AlarmSensorCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class AlarmSensorCCGet extends AlarmSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AlarmSensorCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): AlarmSensorCCGet; // (undocumented) sensorType: AlarmSensorType; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AlarmSensorCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AlarmSensorCCGetOptions extends CCCommandOptions { +export interface AlarmSensorCCGetOptions { // (undocumented) sensorType?: AlarmSensorType; } @@ -188,11 +223,13 @@ export interface AlarmSensorCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class AlarmSensorCCReport extends AlarmSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly duration: number | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): AlarmSensorCCReport; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly sensorType: AlarmSensorType; // (undocumented) @@ -200,7 +237,21 @@ export class AlarmSensorCCReport extends AlarmSensorCC { // (undocumented) readonly state: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "AlarmSensorCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AlarmSensorCCReportOptions { + // (undocumented) + duration?: number; + // (undocumented) + sensorType: AlarmSensorType; + // (undocumented) + severity?: number; + // (undocumented) + state: boolean; } // Warning: (ae-missing-release-tag) "AlarmSensorCCSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -213,13 +264,23 @@ export class AlarmSensorCCSupportedGet extends AlarmSensorCC { // // @public (undocumented) export class AlarmSensorCCSupportedReport extends AlarmSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): AlarmSensorCCSupportedReport; // (undocumented) - get supportedSensorTypes(): readonly AlarmSensorType[]; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + supportedSensorTypes: AlarmSensorType[]; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "AlarmSensorCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AlarmSensorCCSupportedReportOptions { // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + supportedSensorTypes: AlarmSensorType[]; } // Warning: (ae-missing-release-tag) "AlarmSensorCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -243,26 +304,15 @@ export const AlarmSensorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; duration: ((sensorType: AlarmSensorType) => { - readonly meta: { - readonly unit: "s"; - readonly label: `${string} duration`; - readonly description: "For how long the alarm should be active"; - readonly ccSpecific: { - readonly sensorType: AlarmSensorType; - }; - readonly writeable: false; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Alarm Sensor"]; property: "duration"; @@ -274,6 +324,17 @@ export const AlarmSensorCCValues: Readonly<{ readonly property: "duration"; readonly propertyKey: AlarmSensorType; }; + readonly meta: { + readonly unit: "s"; + readonly label: `${string} duration`; + readonly description: "For how long the alarm should be active"; + readonly ccSpecific: { + readonly sensorType: AlarmSensorType; + }; + readonly writeable: false; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -286,6 +347,17 @@ export const AlarmSensorCCValues: Readonly<{ }; }; severity: ((sensorType: AlarmSensorType) => { + readonly id: { + commandClass: (typeof CommandClasses)["Alarm Sensor"]; + property: "severity"; + propertyKey: AlarmSensorType; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses)["Alarm Sensor"]; + readonly endpoint: number; + readonly property: "severity"; + readonly propertyKey: AlarmSensorType; + }; readonly meta: { readonly min: 1; readonly max: 100; @@ -298,17 +370,6 @@ export const AlarmSensorCCValues: Readonly<{ readonly type: "number"; readonly readable: true; }; - readonly id: { - commandClass: (typeof CommandClasses)["Alarm Sensor"]; - property: "severity"; - propertyKey: AlarmSensorType; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses)["Alarm Sensor"]; - readonly endpoint: number; - readonly property: "severity"; - readonly propertyKey: AlarmSensorType; - }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -321,16 +382,6 @@ export const AlarmSensorCCValues: Readonly<{ }; }; state: ((sensorType: AlarmSensorType) => { - readonly meta: { - readonly label: `${string} state`; - readonly description: "Whether the alarm is active"; - readonly ccSpecific: { - readonly sensorType: AlarmSensorType; - }; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Alarm Sensor"]; property: "state"; @@ -342,6 +393,16 @@ export const AlarmSensorCCValues: Readonly<{ readonly property: "state"; readonly propertyKey: AlarmSensorType; }; + readonly meta: { + readonly label: `${string} state`; + readonly description: "Whether the alarm is active"; + readonly ccSpecific: { + readonly sensorType: AlarmSensorType; + }; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -407,23 +468,17 @@ export const API: (cc: CommandClasses_2) => TypedClassDec // Warning: (ae-missing-release-tag) "APIConstructor" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type APIConstructor = new (applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint) => T; +export type APIConstructor = new (host: CCAPIHost, endpoint: CCAPIEndpoint) => T; // Warning: (ae-missing-release-tag) "APIMethodsOf" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type APIMethodsOf = Omit>, "ccId" | "getNode" | "getNodeUnsafe" | "isSetValueOptimistic" | "isSupported" | "pollValue" | "setValue" | "version" | "supportsCommand" | "withOptions" | "withTXReport">; - -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration -// Warning: (ae-missing-release-tag) "assertValidCCs" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function assertValidCCs(container: ICommandClassContainer): void; +export type APIMethodsOf = Omit>, "ccId" | "getNode" | "tryGetNode" | "isSetValueOptimistic" | "isSupported" | "pollValue" | "setValue" | "version" | "supportsCommand" | "withOptions" | "withTXReport">; // Warning: (ae-missing-release-tag) "assignLifelineIssueingCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function assignLifelineIssueingCommand(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, ccId: CommandClasses, ccCommand: number): Promise; +function assignLifelineIssueingCommand(ctx: CCAPIHost & QuerySecurityClasses>, endpoint: EndpointId_2, ccId: CommandClasses, ccCommand: number): Promise; // Warning: (ae-missing-release-tag) "AssociationAddress" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -443,32 +498,34 @@ export class AssociationCC extends CommandClass { ccCommand: AssociationCommand; // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; - static getAllDestinationsCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): ReadonlyMap; - static getGroupCountCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): number; - static getMaxNodesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, groupId: number): number; + static getAllDestinationsCached(ctx: GetValueDB_2, endpoint: EndpointId_2): ReadonlyMap; + static getGroupCountCached(ctx: GetValueDB_2, endpoint: EndpointId_2): number; + static getMaxNodesCached(ctx: GetValueDB_2 & GetDeviceConfig_2, endpoint: EndpointId_2, groupId: number): number; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "AssociationCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class AssociationCCGet extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCGet; // (undocumented) groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationCCGetOptions extends CCCommandOptions { +export interface AssociationCCGetOptions { // (undocumented) groupId: number; } @@ -477,15 +534,17 @@ export interface AssociationCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class AssociationCCRemove extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (AssociationCCRemoveOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCRemove; // (undocumented) groupId?: number; // (undocumented) nodeIds?: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationCCRemoveOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -500,31 +559,33 @@ export interface AssociationCCRemoveOptions { // // @public (undocumented) export class AssociationCCReport extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (AssociationCCReportSpecificOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) groupId: number; // (undocumented) maxNodes: number; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: AssociationCCReport[]): void; + mergePartialCCs(partials: AssociationCCReport[], _ctx: CCParsingContext_2): void; // (undocumented) nodeIds: number[]; // (undocumented) reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } -// Warning: (ae-missing-release-tag) "AssociationCCReportSpecificOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "AssociationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationCCReportSpecificOptions { +export interface AssociationCCReportOptions { // (undocumented) groupId: number; // (undocumented) @@ -539,21 +600,23 @@ export interface AssociationCCReportSpecificOptions { // // @public (undocumented) export class AssociationCCSet extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCSet; // (undocumented) groupId: number; // (undocumented) nodeIds: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationCCSetOptions extends CCCommandOptions { +export interface AssociationCCSetOptions { // (undocumented) groupId: number; // (undocumented) @@ -570,13 +633,15 @@ export class AssociationCCSpecificGroupGet extends AssociationCC { // // @public (undocumented) export class AssociationCCSpecificGroupReport extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (AssociationCCSpecificGroupReportOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCSpecificGroupReport; // (undocumented) group: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationCCSpecificGroupReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -597,19 +662,21 @@ export class AssociationCCSupportedGroupingsGet extends AssociationCC { // // @public (undocumented) export class AssociationCCSupportedGroupingsReport extends AssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationCCSupportedGroupingsReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationCCSupportedGroupingsReport; // (undocumented) groupCount: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationCCSupportedGroupingsReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationCCSupportedGroupingsReportOptions extends CCCommandOptions { +export interface AssociationCCSupportedGroupingsReportOptions { // (undocumented) groupCount: number; } @@ -619,11 +686,6 @@ export interface AssociationCCSupportedGroupingsReportOptions extends CCCommandO // @public (undocumented) export const AssociationCCValues: Readonly<{ nodeIds: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Association; property: "nodeIds"; @@ -635,23 +697,23 @@ export const AssociationCCValues: Readonly<{ readonly property: "nodeIds"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; maxNodes: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Association; property: "maxNodes"; @@ -663,14 +725,19 @@ export const AssociationCCValues: Readonly<{ readonly property: "maxNodes"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -691,11 +758,11 @@ export const AssociationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -716,11 +783,11 @@ export const AssociationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -783,35 +850,37 @@ export class AssociationGroupInfoCC extends CommandClass { // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; // (undocumented) - static findGroupsForIssuedCommand(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, ccId: CommandClasses, command: number): number[]; - static getGroupNameCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, groupId: number): MaybeNotKnown; - static getGroupProfileCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, groupId: number): MaybeNotKnown; - static getIssuedCommandsCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, groupId: number): MaybeNotKnown>; + static findGroupsForIssuedCommand(ctx: GetValueDB_2, endpoint: EndpointId_2 & SupportsCC, ccId: CommandClasses, command: number): number[]; + static getGroupNameCached(ctx: GetValueDB_2, endpoint: EndpointId_2, groupId: number): MaybeNotKnown; + static getGroupProfileCached(ctx: GetValueDB_2, endpoint: EndpointId_2, groupId: number): MaybeNotKnown; + static getIssuedCommandsCached(ctx: GetValueDB_2, endpoint: EndpointId_2, groupId: number): MaybeNotKnown>; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCCommandListGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class AssociationGroupInfoCCCommandListGet extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationGroupInfoCCCommandListGetOptions); + constructor(options: WithAddress); // (undocumented) allowCache: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCCommandListGet; + // (undocumented) groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCCommandListGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationGroupInfoCCCommandListGetOptions extends CCCommandOptions { +export interface AssociationGroupInfoCCCommandListGetOptions { // (undocumented) allowCache: boolean; // (undocumented) @@ -822,21 +891,23 @@ export interface AssociationGroupInfoCCCommandListGetOptions extends CCCommandOp // // @public (undocumented) export class AssociationGroupInfoCCCommandListReport extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationGroupInfoCCCommandListReportOptions); + constructor(options: WithAddress); // (undocumented) readonly commands: ReadonlyMap; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCCommandListReport; + // (undocumented) readonly groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCCommandListReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationGroupInfoCCCommandListReportOptions extends CCCommandOptions { +export interface AssociationGroupInfoCCCommandListReportOptions { // (undocumented) commands: ReadonlyMap; // (undocumented) @@ -847,7 +918,9 @@ export interface AssociationGroupInfoCCCommandListReportOptions extends CCComman // // @public (undocumented) export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationGroupInfoCCInfoGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCInfoGet; // (undocumented) groupId?: number; // (undocumented) @@ -855,15 +928,15 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { // (undocumented) refreshCache: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCInfoGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type AssociationGroupInfoCCInfoGetOptions = CCCommandOptions & { +export type AssociationGroupInfoCCInfoGetOptions = { refreshCache: boolean; } & ({ listMode: boolean; @@ -875,7 +948,9 @@ export type AssociationGroupInfoCCInfoGetOptions = CCCommandOptions & { // // @public (undocumented) export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (AssociationGroupInfoCCInfoReportSpecificOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCInfoReport; // Warning: (ae-forgotten-export) The symbol "AssociationGroupInfo" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -885,17 +960,17 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { // (undocumented) readonly isListMode: boolean; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } -// Warning: (ae-missing-release-tag) "AssociationGroupInfoCCInfoReportSpecificOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "AssociationGroupInfoCCInfoReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationGroupInfoCCInfoReportSpecificOptions { +export interface AssociationGroupInfoCCInfoReportOptions { // (undocumented) groups: AssociationGroupInfo[]; // (undocumented) @@ -908,19 +983,21 @@ export interface AssociationGroupInfoCCInfoReportSpecificOptions { // // @public (undocumented) export class AssociationGroupInfoCCNameGet extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationGroupInfoCCNameGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCNameGet; // (undocumented) groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCNameGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationGroupInfoCCNameGetOptions extends CCCommandOptions { +export interface AssociationGroupInfoCCNameGetOptions { // (undocumented) groupId: number; } @@ -929,23 +1006,25 @@ export interface AssociationGroupInfoCCNameGetOptions extends CCCommandOptions { // // @public (undocumented) export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | AssociationGroupInfoCCNameReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): AssociationGroupInfoCCNameReport; // (undocumented) readonly groupId: number; // (undocumented) readonly name: string; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "AssociationGroupInfoCCNameReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface AssociationGroupInfoCCNameReportOptions extends CCCommandOptions { +export interface AssociationGroupInfoCCNameReportOptions { // (undocumented) groupId: number; // (undocumented) @@ -957,11 +1036,6 @@ export interface AssociationGroupInfoCCNameReportOptions extends CCCommandOption // @public (undocumented) export const AssociationGroupInfoCCValues: Readonly<{ commands: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Association Group Information"]; property: "issuedCommands"; @@ -973,23 +1047,23 @@ export const AssociationGroupInfoCCValues: Readonly<{ readonly property: "issuedCommands"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; groupInfo: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Association Group Information"]; property: "info"; @@ -1001,23 +1075,23 @@ export const AssociationGroupInfoCCValues: Readonly<{ readonly property: "info"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; groupName: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Association Group Information"]; property: "name"; @@ -1029,14 +1103,19 @@ export const AssociationGroupInfoCCValues: Readonly<{ readonly property: "name"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -1057,11 +1136,11 @@ export const AssociationGroupInfoCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -1444,28 +1523,30 @@ export class BarrierOperatorCC extends CommandClass { // (undocumented) ccCommand: BarrierOperatorCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCEventSignalingGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BarrierOperatorCCEventSignalingGet extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BarrierOperatorCCEventSignalingGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): BarrierOperatorCCEventSignalingGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) subsystemType: SubsystemType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCEventSignalingGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BarrierOperatorCCEventSignalingGetOptions extends CCCommandOptions { +export interface BarrierOperatorCCEventSignalingGetOptions { // (undocumented) subsystemType: SubsystemType; } @@ -1474,36 +1555,50 @@ export interface BarrierOperatorCCEventSignalingGetOptions extends CCCommandOpti // // @public (undocumented) export class BarrierOperatorCCEventSignalingReport extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BarrierOperatorCCEventSignalingReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly subsystemState: SubsystemState; // (undocumented) readonly subsystemType: SubsystemType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "BarrierOperatorCCEventSignalingReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface BarrierOperatorCCEventSignalingReportOptions { + // (undocumented) + subsystemState: SubsystemState; + // (undocumented) + subsystemType: SubsystemType; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCEventSignalingSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BarrierOperatorCCEventSignalingSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): BarrierOperatorCCEventSignalingSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) subsystemState: SubsystemState; // (undocumented) subsystemType: SubsystemType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCEventSignalingSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BarrierOperatorCCEventSignalingSetOptions extends CCCommandOptions { +export interface BarrierOperatorCCEventSignalingSetOptions { // (undocumented) subsystemState: SubsystemState; // (undocumented) @@ -1520,32 +1615,46 @@ export class BarrierOperatorCCGet extends BarrierOperatorCC { // // @public (undocumented) export class BarrierOperatorCCReport extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly currentState: MaybeUnknown; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BarrierOperatorCCReport; + // (undocumented) readonly position: MaybeUnknown; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "BarrierOperatorCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface BarrierOperatorCCReportOptions { + // (undocumented) + currentState: MaybeUnknown; + // (undocumented) + position: MaybeUnknown; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BarrierOperatorCCSet extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BarrierOperatorCCSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): BarrierOperatorCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetState: BarrierState.Open | BarrierState.Closed; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BarrierOperatorCCSetOptions extends CCCommandOptions { +export interface BarrierOperatorCCSetOptions { // (undocumented) targetState: BarrierState.Open | BarrierState.Closed; } @@ -1560,11 +1669,21 @@ export class BarrierOperatorCCSignalingCapabilitiesGet extends BarrierOperatorCC // // @public (undocumented) export class BarrierOperatorCCSignalingCapabilitiesReport extends BarrierOperatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BarrierOperatorCCSignalingCapabilitiesReport; // (undocumented) readonly supportedSubsystemTypes: readonly SubsystemType[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "BarrierOperatorCCSignalingCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface BarrierOperatorCCSignalingCapabilitiesReportOptions { + // (undocumented) + supportedSubsystemTypes: SubsystemType[]; } // Warning: (ae-missing-release-tag) "BarrierOperatorCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1572,17 +1691,6 @@ export class BarrierOperatorCCSignalingCapabilitiesReport extends BarrierOperato // @public (undocumented) export const BarrierOperatorCCValues: Readonly<{ signalingState: ((subsystemType: SubsystemType) => { - readonly meta: { - readonly label: `Signaling State (${string})`; - readonly states: { - [x: number]: string; - }; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Barrier Operator"]; property: "signalingState"; @@ -1594,6 +1702,17 @@ export const BarrierOperatorCCValues: Readonly<{ readonly property: "signalingState"; readonly propertyKey: SubsystemType; }; + readonly meta: { + readonly label: `Signaling State (${string})`; + readonly states: { + [x: number]: string; + }; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -1713,11 +1832,11 @@ export const BarrierOperatorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -1768,11 +1887,11 @@ export class BasicCC extends CommandClass { // (undocumented) ccCommand: BasicCommand; // (undocumented) - getDefinedValueIDs(applHost: ZWaveApplicationHost_2): ValueID_2[]; + getDefinedValueIDs(ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): ValueID_2[]; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "BasicCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1785,48 +1904,54 @@ export class BasicCCGet extends BasicCC { // // @public (undocumented) export class BasicCCReport extends BasicCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BasicCCReportOptions); + constructor(options: WithAddress); // (undocumented) - get currentValue(): MaybeUnknown | undefined; + currentValue: MaybeUnknown | undefined; // (undocumented) readonly duration: Duration | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): BasicCCReport; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly targetValue: MaybeUnknown | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BasicCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type BasicCCReportOptions = CCCommandOptions & { - currentValue: number; -} & AllOrNone<{ - targetValue: number; - duration: Duration; -}>; +export interface BasicCCReportOptions { + // (undocumented) + currentValue?: MaybeUnknown; + // (undocumented) + duration?: Duration; + // (undocumented) + targetValue?: MaybeUnknown; +} // Warning: (ae-missing-release-tag) "BasicCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BasicCCSet extends BasicCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BasicCCSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): BasicCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetValue: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BasicCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BasicCCSetOptions extends CCCommandOptions { +export interface BasicCCSetOptions { // (undocumented) targetValue: number; } @@ -1855,10 +1980,10 @@ export const BasicCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly secret: false; readonly internal: false; - readonly minVersion: 1; readonly supportsEndpoints: true; + readonly secret: false; + readonly minVersion: 1; readonly stateful: false; readonly autoCreate: false; }; @@ -1997,11 +2122,11 @@ export class BatteryCC extends CommandClass { // (undocumented) ccCommand: BatteryCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - shouldRefreshValues(this: SinglecastCC_2, applHost: ZWaveApplicationHost_2): boolean; + shouldRefreshValues(this: SinglecastCC_2, ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): boolean; } // Warning: (ae-missing-release-tag) "BatteryCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2020,29 +2145,45 @@ export class BatteryCCHealthGet extends BatteryCC { // // @public (undocumented) export class BatteryCCHealthReport extends BatteryCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BatteryCCHealthReport; // (undocumented) readonly maximumCapacity: number | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly temperature: number | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } -// Warning: (ae-missing-release-tag) "BatteryCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "BatteryCCHealthReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class BatteryCCReport extends BatteryCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BatteryCCReportOptions); +export interface BatteryCCHealthReportOptions { // (undocumented) - readonly backup: boolean | undefined; + maximumCapacity?: number; // (undocumented) - readonly chargingStatus: BatteryChargingStatus | undefined; + temperature?: number; + // (undocumented) + temperatureScale?: number; +} + +// Warning: (ae-missing-release-tag) "BatteryCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class BatteryCCReport extends BatteryCC { + constructor(options: WithAddress_2); + // (undocumented) + readonly backup: boolean | undefined; + // (undocumented) + readonly chargingStatus: BatteryChargingStatus | undefined; // (undocumented) readonly disconnected: boolean | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BatteryCCReport; + // (undocumented) readonly isLow: boolean; // (undocumented) readonly level: number; @@ -2053,21 +2194,21 @@ export class BatteryCCReport extends BatteryCC { // (undocumented) readonly overheating: boolean | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly rechargeable: boolean | undefined; // (undocumented) readonly rechargeOrReplace: BatteryReplacementStatus | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BatteryCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type BatteryCCReportOptions = CCCommandOptions & ({ +export type BatteryCCReportOptions = ({ isLow?: false; level: number; } | { @@ -2107,11 +2248,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; }; }; @@ -2133,11 +2274,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2164,11 +2305,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2190,11 +2331,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2216,11 +2357,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2242,11 +2383,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2268,11 +2409,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2299,11 +2440,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2327,11 +2468,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2356,11 +2497,11 @@ export const BatteryCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2465,32 +2606,34 @@ export enum BatteryReplacementStatus { export class BinarySensorCC extends CommandClass { // (undocumented) ccCommand: BinarySensorCommand; - static getSupportedSensorTypesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; + static getSupportedSensorTypesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - setMappedBasicValue(applHost: ZWaveApplicationHost_2, value: number): boolean; + setMappedBasicValue(ctx: GetValueDB_2, value: number): boolean; } // Warning: (ae-missing-release-tag) "BinarySensorCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BinarySensorCCGet extends BinarySensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BinarySensorCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BinarySensorCCGet; // (undocumented) sensorType: BinarySensorType | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BinarySensorCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BinarySensorCCGetOptions extends CCCommandOptions { +export interface BinarySensorCCGetOptions { // (undocumented) sensorType?: BinarySensorType; } @@ -2499,13 +2642,15 @@ export interface BinarySensorCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class BinarySensorCCReport extends BinarySensorCC { - constructor(host: ZWaveHost_2, options: BinarySensorCCReportOptions | CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): BinarySensorCCReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: BinarySensorType; // (undocumented) @@ -2515,7 +2660,7 @@ export class BinarySensorCCReport extends BinarySensorCC { // Warning: (ae-missing-release-tag) "BinarySensorCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BinarySensorCCReportOptions extends CCCommandOptions { +export interface BinarySensorCCReportOptions { // (undocumented) type?: BinarySensorType; // (undocumented) @@ -2532,13 +2677,15 @@ export class BinarySensorCCSupportedGet extends BinarySensorCC { // // @public (undocumented) export class BinarySensorCCSupportedReport extends BinarySensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (BinarySensorCCSupportedReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): BinarySensorCCSupportedReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportedSensorTypes: BinarySensorType[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BinarySensorCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2554,15 +2701,6 @@ export interface BinarySensorCCSupportedReportOptions { // @public (undocumented) export const BinarySensorCCValues: Readonly<{ state: ((sensorType: BinarySensorType) => { - readonly meta: { - readonly label: `Sensor state (${string})`; - readonly ccSpecific: { - readonly sensorType: BinarySensorType; - }; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Binary Sensor"]; property: string; @@ -2572,6 +2710,15 @@ export const BinarySensorCCValues: Readonly<{ readonly endpoint: number; readonly property: string; }; + readonly meta: { + readonly label: `Sensor state (${string})`; + readonly ccSpecific: { + readonly sensorType: BinarySensorType; + }; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -2600,11 +2747,11 @@ export const BinarySensorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -2674,11 +2821,11 @@ export class BinarySwitchCC extends CommandClass { // (undocumented) ccCommand: BinarySwitchCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - setMappedBasicValue(applHost: ZWaveApplicationHost_2, value: number): boolean; + setMappedBasicValue(ctx: GetValueDB_2, value: number): boolean; } // Warning: (ae-missing-release-tag) "BinarySwitchCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2691,48 +2838,54 @@ export class BinarySwitchCCGet extends BinarySwitchCC { // // @public (undocumented) export class BinarySwitchCCReport extends BinarySwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BinarySwitchCCReportOptions); + constructor(options: WithAddress); // (undocumented) readonly currentValue: MaybeUnknown | undefined; // (undocumented) readonly duration: Duration | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): BinarySwitchCCReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly targetValue: MaybeUnknown | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BinarySwitchCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type BinarySwitchCCReportOptions = CCCommandOptions & { - currentValue: MaybeUnknown; -} & AllOrNone_2<{ - targetValue: MaybeUnknown; - duration: Duration | string; -}>; +export interface BinarySwitchCCReportOptions { + // (undocumented) + currentValue?: MaybeUnknown; + // (undocumented) + duration?: Duration | string; + // (undocumented) + targetValue?: MaybeUnknown; +} // Warning: (ae-missing-release-tag) "BinarySwitchCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class BinarySwitchCCSet extends BinarySwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | BinarySwitchCCSetOptions); + constructor(options: WithAddress); // (undocumented) duration: Duration | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): BinarySwitchCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetValue: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "BinarySwitchCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface BinarySwitchCCSetOptions extends CCCommandOptions { +export interface BinarySwitchCCSetOptions { // (undocumented) duration?: Duration | string; // (undocumented) @@ -2761,11 +2914,11 @@ export const BinarySwitchCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -2847,37 +3000,36 @@ export class CCAPI { protected get [SET_VALUE](): SetValueImplementation | undefined; // (undocumented) protected [SET_VALUE_HOOKS]: SetValueImplementationHooksFactory | undefined; - constructor(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint); - // (undocumented) - protected readonly applHost: ZWaveApplicationHost; + constructor(host: CCAPIHost, endpoint: CCAPIEndpoint); // (undocumented) - protected assertPhysicalEndpoint(endpoint: IZWaveEndpoint | IVirtualEndpoint): asserts endpoint is IZWaveEndpoint; + protected assertPhysicalEndpoint(endpoint: EndpointId | VirtualEndpointId): asserts endpoint is EndpointId; // (undocumented) protected assertSupportsCommand(commandEnum: unknown, command: number): void; readonly ccId: CommandClasses_2; protected get commandOptions(): SendCommandOptions; // (undocumented) - static create(ccId: T, applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint, requireSupport?: boolean): CommandClasses_2 extends T ? CCAPI : CCToAPI; + static create(ccId: T, host: CCAPIHost, endpoint: CCAPIEndpoint, requireSupport?: boolean): CommandClasses_2 extends T ? CCAPI : CCToAPI; // (undocumented) - protected readonly endpoint: IZWaveEndpoint | IVirtualEndpoint; - getNode(): IZWaveNode_2 | undefined; + protected readonly endpoint: CCAPIEndpoint; protected getValueDB(): ValueDB; // (undocumented) + protected readonly host: CCAPIHost; + // (undocumented) protected isBroadcast(): this is this & { - endpoint: IVirtualEndpoint & { + endpoint: VirtualCCAPIEndpoint & { nodeId: typeof NODE_ID_BROADCAST | typeof NODE_ID_BROADCAST_LR; }; }; // (undocumented) protected isMulticast(): this is this & { - endpoint: IVirtualEndpoint & { + endpoint: VirtualCCAPIEndpoint & { nodeId: number[]; }; }; isSetValueOptimistic(valueId: ValueID): boolean; // (undocumented) protected isSinglecast(): this is this & { - endpoint: IZWaveEndpoint; + endpoint: PhysicalCCAPIEndpoint; }; isSupported(): boolean; get pollValue(): PollValueImplementation | undefined; @@ -2892,6 +3044,23 @@ export class CCAPI { withTXReport(): WithTXReport; } +// Warning: (ae-missing-release-tag) "CCAPIEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CCAPIEndpoint = ((EndpointId & ControlsCC_2) | (VirtualEndpointId & { + node: PhysicalNodes>; +})) & SupportsCC_2; + +// Warning: (ae-missing-release-tag) "CCAPIHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CCAPIHost = HostIDs & GetNode & GetValueDB & GetSupportedCCVersion & GetSafeCCVersion & SecurityManagers & GetDeviceConfig & SendCommand & GetCommunicationTimeouts & GetUserPreferences & SchedulePoll & LogNode; + +// Warning: (ae-missing-release-tag) "CCAPINode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CCAPINode = NodeId_2 & ListenBehavior & QueryNodeStatus; + // Warning: (ae-missing-release-tag) "CCAPIs" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3030,28 +3199,45 @@ export interface CCAPIs { // @public export const CCCommand: (ccCommand: number) => TypedClassDecorator_2; -// Warning: (ae-missing-release-tag) "CCCommandOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface CCCommandOptions { - // (undocumented) - endpoint?: number; - // (undocumented) - nodeId: number | MulticastDestination; -} - // Warning: (ae-missing-release-tag) "CCConstructor" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export type CCConstructor = typeof CommandClass & { - new (host: ZWaveHost, options: any): T; + new (options: any): T; }; +// Warning: (ae-missing-release-tag) "CCEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CCEndpoint = EndpointId & SupportsCC_2 & ControlsCC_2 & GetCCs & ModifyCCs; + // Warning: (ae-missing-release-tag) "CCNameOrId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export type CCNameOrId = CommandClasses_2 | Extract; +// Warning: (ae-missing-release-tag) "CCNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CCNode = NodeId_2 & SupportsCC_2 & ControlsCC_2 & GetCCs & GetEndpoint_2 & GetAllEndpoints_2 & QuerySecurityClasses_2 & SetSecurityClass & ListenBehavior & QueryNodeStatus; + +// Warning: (ae-missing-release-tag) "CCRaw" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class CCRaw { + constructor(ccId: CommandClasses_2, ccCommand: number | undefined, payload: Buffer); + // (undocumented) + ccCommand: number | undefined; + // (undocumented) + ccId: CommandClasses_2; + // (undocumented) + static parse(data: Buffer): CCRaw; + // (undocumented) + payload: Buffer; + // (undocumented) + withPayload(payload: Buffer): CCRaw; +} + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "CCResponsePredicate" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -3094,7 +3280,7 @@ export function ccValue(value // // @public (undocumented) export interface CCValueOptions { - autoCreate?: boolean | ((applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint) => boolean); + autoCreate?: boolean | ((ctx: GetValueDB & GetDeviceConfig, endpoint: EndpointId) => boolean); internal?: boolean; minVersion?: number; secret?: boolean; @@ -3122,7 +3308,7 @@ export class CentralSceneCC extends CommandClass { // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) skipEndpointInterview(): boolean; } @@ -3137,30 +3323,42 @@ export class CentralSceneCCConfigurationGet extends CentralSceneCC { // // @public (undocumented) export class CentralSceneCCConfigurationReport extends CentralSceneCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): CentralSceneCCConfigurationReport; // (undocumented) readonly slowRefresh: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "CentralSceneCCConfigurationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CentralSceneCCConfigurationReportOptions { + // (undocumented) + slowRefresh: boolean; } // Warning: (ae-missing-release-tag) "CentralSceneCCConfigurationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class CentralSceneCCConfigurationSet extends CentralSceneCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CentralSceneCCConfigurationSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): CentralSceneCCConfigurationSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) slowRefresh: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "CentralSceneCCConfigurationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface CentralSceneCCConfigurationSetOptions extends CCCommandOptions { +export interface CentralSceneCCConfigurationSetOptions { // (undocumented) slowRefresh: boolean; } @@ -3169,11 +3367,13 @@ export interface CentralSceneCCConfigurationSetOptions extends CCCommandOptions // // @public (undocumented) export class CentralSceneCCNotification extends CentralSceneCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): CentralSceneCCNotification; // (undocumented) readonly keyAttribute: CentralSceneKeys; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly sceneNumber: number; // (undocumented) @@ -3181,7 +3381,21 @@ export class CentralSceneCCNotification extends CentralSceneCC { // (undocumented) readonly slowRefresh: boolean | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "CentralSceneCCNotificationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CentralSceneCCNotificationOptions { + // (undocumented) + keyAttribute: CentralSceneKeys; + // (undocumented) + sceneNumber: number; + // (undocumented) + sequenceNumber: number; + // (undocumented) + slowRefresh?: boolean; } // Warning: (ae-missing-release-tag) "CentralSceneCCSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3194,9 +3408,11 @@ export class CentralSceneCCSupportedGet extends CentralSceneCC { // // @public (undocumented) export class CentralSceneCCSupportedReport extends CentralSceneCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): CentralSceneCCSupportedReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly sceneCount: number; // (undocumented) @@ -3204,7 +3420,19 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { // (undocumented) readonly supportsSlowRefresh: MaybeNotKnown; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "CentralSceneCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CentralSceneCCSupportedReportOptions { + // (undocumented) + sceneCount: number; + // (undocumented) + supportedKeyAttributes: Record; + // (undocumented) + supportsSlowRefresh: MaybeNotKnown; } // Warning: (ae-missing-release-tag) "CentralSceneCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3212,14 +3440,6 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { // @public (undocumented) export const CentralSceneCCValues: Readonly<{ scene: ((sceneNumber: number) => { - readonly meta: { - readonly label: `Scene ${string}`; - readonly writeable: false; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Central Scene"]; property: "scene"; @@ -3231,14 +3451,22 @@ export const CentralSceneCCValues: Readonly<{ readonly property: "scene"; readonly propertyKey: string; }; + readonly meta: { + readonly label: `Scene ${string}`; + readonly writeable: false; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { - readonly secret: false; readonly internal: false; - readonly minVersion: 1; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly secret: false; + readonly minVersion: 1; readonly stateful: false; }; }; @@ -3286,11 +3514,11 @@ export const CentralSceneCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -3311,11 +3539,11 @@ export const CentralSceneCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -3336,11 +3564,11 @@ export const CentralSceneCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -3387,7 +3615,7 @@ export enum CentralSceneKeys { // Warning: (ae-missing-release-tag) "checkAssociation" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function checkAssociation(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, group: number, destination: AssociationAddress): AssociationCheckResult; +function checkAssociation(ctx: HostIDs & GetValueDB & GetNode & QuerySecurityClasses>, endpoint: EndpointId_2 & SupportsCC & ControlsCC, group: number, destination: AssociationAddress): AssociationCheckResult; // Warning: (ae-missing-release-tag) "ClimateControlScheduleCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -3407,22 +3635,34 @@ export class ClimateControlScheduleCCChangedGet extends ClimateControlScheduleCC // // @public (undocumented) export class ClimateControlScheduleCCChangedReport extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly changeCounter: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + static from(raw: CCRaw, ctx: CCParsingContext_2): ClimateControlScheduleCCChangedReport; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ClimateControlScheduleCCChangedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClimateControlScheduleCCChangedReportOptions { + // (undocumented) + changeCounter: number; } // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ClimateControlScheduleCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ClimateControlScheduleCCGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) weekday: Weekday; } @@ -3430,7 +3670,7 @@ export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ClimateControlScheduleCCGetOptions extends CCCommandOptions { +export interface ClimateControlScheduleCCGetOptions { // (undocumented) weekday: Weekday; } @@ -3445,34 +3685,48 @@ export class ClimateControlScheduleCCOverrideGet extends ClimateControlScheduleC // // @public (undocumented) export class ClimateControlScheduleCCOverrideReport extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ClimateControlScheduleCCOverrideReport; // (undocumented) readonly overrideState: SetbackState; // (undocumented) readonly overrideType: ScheduleOverrideType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ClimateControlScheduleCCOverrideReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClimateControlScheduleCCOverrideReportOptions { + // (undocumented) + overrideState: SetbackState; + // (undocumented) + overrideType: ScheduleOverrideType; } // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCOverrideSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ClimateControlScheduleCCOverrideSet extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ClimateControlScheduleCCOverrideSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ClimateControlScheduleCCOverrideSet; // (undocumented) overrideState: SetbackState; // (undocumented) overrideType: ScheduleOverrideType; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCOverrideSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ClimateControlScheduleCCOverrideSetOptions extends CCCommandOptions { +export interface ClimateControlScheduleCCOverrideSetOptions { // (undocumented) overrideState: SetbackState; // (undocumented) @@ -3483,26 +3737,40 @@ export interface ClimateControlScheduleCCOverrideSetOptions extends CCCommandOpt // // @public (undocumented) export class ClimateControlScheduleCCReport extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ClimateControlScheduleCCReport; // (undocumented) readonly schedule: readonly Switchpoint[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly weekday: Weekday; } +// Warning: (ae-missing-release-tag) "ClimateControlScheduleCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClimateControlScheduleCCReportOptions { + // (undocumented) + schedule: Switchpoint[]; + // (undocumented) + weekday: Weekday; +} + // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ClimateControlScheduleCCSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ClimateControlScheduleCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) switchPoints: Switchpoint[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) weekday: Weekday; } @@ -3510,7 +3778,7 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { // Warning: (ae-missing-release-tag) "ClimateControlScheduleCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ClimateControlScheduleCCSetOptions extends CCCommandOptions { +export interface ClimateControlScheduleCCSetOptions { // (undocumented) switchPoints: Switchpoint[]; // (undocumented) @@ -3522,12 +3790,6 @@ export interface ClimateControlScheduleCCSetOptions extends CCCommandOptions { // @public (undocumented) export const ClimateControlScheduleCCValues: Readonly<{ schedule: ((weekday: Weekday) => { - readonly meta: { - readonly label: `Schedule (${string})`; - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Climate Control Schedule"]; property: "schedule"; @@ -3539,6 +3801,12 @@ export const ClimateControlScheduleCCValues: Readonly<{ readonly property: "schedule"; readonly propertyKey: Weekday; }; + readonly meta: { + readonly label: `Schedule (${string})`; + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -3637,9 +3905,9 @@ export class ClockCC extends CommandClass { // (undocumented) ccCommand: ClockCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ClockCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3652,30 +3920,46 @@ export class ClockCCGet extends ClockCC { // // @public (undocumented) export class ClockCCReport extends ClockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ClockCCReport; // (undocumented) readonly hour: number; // (undocumented) readonly minute: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly weekday: Weekday; } +// Warning: (ae-missing-release-tag) "ClockCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClockCCReportOptions { + // (undocumented) + hour: number; + // (undocumented) + minute: number; + // (undocumented) + weekday: Weekday; +} + // Warning: (ae-missing-release-tag) "ClockCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ClockCCSet extends ClockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ClockCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ClockCCSet; // (undocumented) hour: number; // (undocumented) minute: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) weekday: Weekday; } @@ -3683,7 +3967,7 @@ export class ClockCCSet extends ClockCC { // Warning: (ae-missing-release-tag) "ClockCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ClockCCSetOptions extends CCCommandOptions { +export interface ClockCCSetOptions { // (undocumented) hour: number; // (undocumented) @@ -3755,31 +4039,33 @@ export class ColorSwitchCC extends CommandClass { // (undocumented) ccCommand: ColorSwitchCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "ColorSwitchCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ColorSwitchCCGet extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ColorSwitchCCGetOptions); + constructor(options: WithAddress_2); // (undocumented) get colorComponent(): ColorComponent; set colorComponent(value: ColorComponent); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ColorSwitchCCGetOptions extends CCCommandOptions { +export interface ColorSwitchCCGetOptions { // (undocumented) colorComponent: ColorComponent; } @@ -3788,7 +4074,7 @@ export interface ColorSwitchCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class ColorSwitchCCReport extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (ColorSwitchCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress_2); // (undocumented) readonly colorComponent: ColorComponent; // (undocumented) @@ -3796,39 +4082,46 @@ export class ColorSwitchCCReport extends ColorSwitchCC { // (undocumented) readonly duration: Duration_2 | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCReport; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly targetValue: number | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ColorSwitchCCReportOptions = { +export interface ColorSwitchCCReportOptions { + // (undocumented) colorComponent: ColorComponent; + // (undocumented) currentValue: number; -} & AllOrNone<{ - targetValue: number; - duration: Duration_2 | string; -}>; + // (undocumented) + duration?: Duration_2 | string; + // (undocumented) + targetValue?: number; +} // Warning: (ae-missing-release-tag) "ColorSwitchCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ColorSwitchCCSet extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & ColorSwitchCCSetOptions)); + constructor(options: WithAddress_2); // (undocumented) colorTable: ColorTable; // (undocumented) duration: Duration_2 | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCSet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3844,7 +4137,7 @@ export type ColorSwitchCCSetOptions = (ColorTable | { // // @public (undocumented) export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & ColorSwitchCCStartLevelChangeOptions)); + constructor(options: WithAddress_2); // (undocumented) colorComponent: ColorComponent; // (undocumented) @@ -3852,13 +4145,15 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { // (undocumented) duration: Duration_2 | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCStartLevelChange; + // (undocumented) ignoreStartLevel: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) startLevel: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCStartLevelChangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3881,19 +4176,21 @@ export type ColorSwitchCCStartLevelChangeOptions = { // // @public (undocumented) export class ColorSwitchCCStopLevelChange extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ColorSwitchCCStopLevelChangeOptions); + constructor(options: WithAddress_2); // (undocumented) readonly colorComponent: ColorComponent; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCStopLevelChange; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCStopLevelChangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ColorSwitchCCStopLevelChangeOptions extends CCCommandOptions { +export interface ColorSwitchCCStopLevelChangeOptions { // (undocumented) colorComponent: ColorComponent; } @@ -3908,13 +4205,15 @@ export class ColorSwitchCCSupportedGet extends ColorSwitchCC { // // @public (undocumented) export class ColorSwitchCCSupportedReport extends ColorSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (ColorSwitchCCSupportedReportOptions & CCCommandOptions)); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ColorSwitchCCSupportedReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedColorComponents: readonly ColorComponent[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ColorSwitchCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -3930,16 +4229,6 @@ export interface ColorSwitchCCSupportedReportOptions { // @public (undocumented) export const ColorSwitchCCValues: Readonly<{ targetColorChannel: ((component: ColorComponent) => { - readonly meta: { - readonly label: `Target value (${string})`; - readonly description: `The target value of the ${string} channel.`; - readonly valueChangeOptions: readonly ["transitionDuration"]; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Color Switch"]; property: "targetColor"; @@ -3951,6 +4240,16 @@ export const ColorSwitchCCValues: Readonly<{ readonly property: "targetColor"; readonly propertyKey: ColorComponent; }; + readonly meta: { + readonly label: `Target value (${string})`; + readonly description: `The target value of the ${string} channel.`; + readonly valueChangeOptions: readonly ["transitionDuration"]; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -3963,15 +4262,6 @@ export const ColorSwitchCCValues: Readonly<{ }; }; currentColorChannel: ((component: ColorComponent) => { - readonly meta: { - readonly label: `Current value (${string})`; - readonly description: `The current value of the ${string} channel.`; - readonly writeable: false; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Color Switch"]; property: "currentColor"; @@ -3983,6 +4273,15 @@ export const ColorSwitchCCValues: Readonly<{ readonly property: "currentColor"; readonly propertyKey: ColorComponent; }; + readonly meta: { + readonly label: `Current value (${string})`; + readonly description: `The current value of the ${string} channel.`; + readonly writeable: false; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -4119,11 +4418,11 @@ export const ColorSwitchCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -4144,11 +4443,11 @@ export const ColorSwitchCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -4182,55 +4481,42 @@ export type ColorTable = Partial> | Partial(host: ZWaveHost, endpoint: IZWaveEndpoint, cc: CommandClasses_2 | CCConstructor): T | undefined; - protected deserialize(data: Buffer): { - ccId: CommandClasses_2; - ccCommand: number; - payload: Buffer; - } | { - ccId: CommandClasses_2; - payload: Buffer; - ccCommand?: undefined; - }; + static createInstanceUnchecked(endpoint: EndpointId, cc: CommandClasses_2 | CCConstructor): T | undefined; determineRequiredCCInterviews(): readonly CommandClasses_2[]; encapsulatingCC?: EncapsulatingCommandClass; encapsulationFlags: EncapsulationFlags; endpointIndex: number; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-forgotten-export) The symbol "CCValue" needs to be exported by the entry point index.d.ts - protected ensureMetadata(host: ZWaveValueHost, ccValue: CCValue, meta?: ValueMetadata): void; + protected ensureMetadata(ctx: GetValueDB, ccValue: CCValue, meta?: ValueMetadata): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen expectMoreMessages(_session: CommandClass[]): boolean; expectsCCResponse(): boolean; readonly frameType?: FrameType; - static from(host: ZWaveHost, options: CommandClassDeserializationOptions): CommandClass; - static getCCCommand(data: Buffer): number | undefined; + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): CommandClass; protected getCCValue(valueId: ValueID): StaticCCValue | DynamicCCValue | undefined; - static getCommandClass(data: Buffer): CommandClasses_2; - static getConstructor(ccData: Buffer): CCConstructor; - getDefinedValueIDs(applHost: ZWaveApplicationHost, includeInternal?: boolean): ValueID[]; + getDefinedValueIDs(ctx: GetValueDB & GetSupportedCCVersion & GetDeviceConfig & GetNode>, includeInternal?: boolean): ValueID[]; getEncapsulatedCC(ccId: CommandClasses_2, ccCommand?: number): CommandClass | undefined; getEncapsulatingCC(ccId: CommandClasses_2, ccCommand?: number): CommandClass | undefined; // (undocumented) - getEndpoint(applHost: ZWaveApplicationHost): IZWaveEndpoint | undefined; + getEndpoint(ctx: GetNode>): T | undefined; getMaxPayloadLength(baseLength: number): number; - protected getMetadata(host: ZWaveValueHost, ccValue: CCValue): T | undefined; - getNode(applHost: ZWaveApplicationHost): IZWaveNode_2 | undefined; + protected getMetadata(ctx: GetValueDB, ccValue: CCValue): T | undefined; + getNode(ctx: GetNode): T | undefined; getPartialCCSessionId(): Record | undefined; - protected getValue(host: ZWaveValueHost, ccValue: CCValue): T | undefined; - protected getValueDB(host: ZWaveValueHost): ValueDB; - protected getValueTimestamp(host: ZWaveValueHost, ccValue: CCValue): number | undefined; - // (undocumented) - protected host: ZWaveHost; - interview(_applHost: ZWaveApplicationHost): Promise; + protected getValue(ctx: GetValueDB, ccValue: CCValue): T | undefined; + protected getValueDB(ctx: GetValueDB): ValueDB; + protected getValueTimestamp(ctx: GetValueDB, ccValue: CCValue): number | undefined; + interview(_ctx: InterviewContext): Promise; // (undocumented) isBroadcast(): this is BroadcastCC; isEncapsulatedWith(ccId: CommandClasses_2, ccCommand?: number): boolean; @@ -4238,45 +4524,46 @@ export class CommandClass implements ICommandClass { isExpectedCCResponse(received: CommandClass): boolean; isExtended(): boolean; isInternalValue(properties: ValueIDProperties): boolean; - isInterviewComplete(host: ZWaveValueHost): boolean; + isInterviewComplete(host: GetValueDB): boolean; // (undocumented) isMulticast(): this is MulticastCC; isSecretValue(properties: ValueIDProperties): boolean; // (undocumented) isSinglecast(): this is SinglecastCC; isStatefulValue(properties: ValueIDProperties): boolean; - mergePartialCCs(_applHost: ZWaveApplicationHost, _partials: CommandClass[]): void; + mergePartialCCs(_partials: CommandClass[], _ctx: CCParsingContext): void; nodeId: number | MulticastDestination; // (undocumented) + static parse(data: Buffer, ctx: CCParsingContext): CommandClass; + // (undocumented) payload: Buffer; // Warning: (tsdoc-characters-after-block-tag) The token "@ccValue" looks like a TSDoc tag but contains an invalid character "."; if it is not a tag, use a backslash to escape the "@" - persistValues(applHost: ZWaveApplicationHost): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) prepareRetransmission(): void; - refreshValues(_applHost: ZWaveApplicationHost): Promise; - protected removeMetadata(host: ZWaveValueHost, ccValue: CCValue): void; - protected removeValue(host: ZWaveValueHost, ccValue: CCValue): void; - serialize(): Buffer; - setInterviewComplete(host: ZWaveValueHost, complete: boolean): void; + refreshValues(_ctx: RefreshValuesContext): Promise; + protected removeMetadata(ctx: GetValueDB, ccValue: CCValue): void; + protected removeValue(ctx: GetValueDB, ccValue: CCValue): void; + serialize(ctx: CCEncodingContext): Buffer; + setInterviewComplete(host: GetValueDB, complete: boolean): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - setMappedBasicValue(_applHost: ZWaveApplicationHost, _value: number): boolean; + setMappedBasicValue(_ctx: GetValueDB, _value: number): boolean; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - protected setMetadata(host: ZWaveValueHost, ccValue: CCValue, meta?: ValueMetadata): void; - protected setValue(host: ZWaveValueHost, ccValue: CCValue, value: unknown): void; - shouldRefreshValues(this: SinglecastCC, _applHost: ZWaveApplicationHost): boolean; + protected setMetadata(ctx: GetValueDB, ccValue: CCValue, meta?: ValueMetadata): void; + protected setValue(ctx: GetValueDB, ccValue: CCValue, value: unknown): void; + shouldRefreshValues(this: SinglecastCC, _ctx: GetValueDB & GetSupportedCCVersion & GetDeviceConfig & GetNode>): boolean; skipEndpointInterview(): boolean; // (undocumented) protected throwMissingCriticalInterviewResponse(): never; toggleEncapsulationFlag(flag: EncapsulationFlags, active: boolean): void; toJSON(): JSONObject; - toLogEntry(_host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(_ctx?: GetValueDB): MessageOrCCLogEntry; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - translateProperty(_applHost: ZWaveApplicationHost, property: string | number, _propertyKey?: string | number): string; + translateProperty(_ctx: GetValueDB, property: string | number, _propertyKey?: string | number): string; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - translatePropertyKey(_applHost: ZWaveApplicationHost, _property: string | number, propertyKey: string | number): string | undefined; - version: number; + translatePropertyKey(_ctx: GetValueDB, _property: string | number, propertyKey: string | number): string | undefined; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration @@ -4285,26 +4572,17 @@ export class CommandClass implements ICommandClass { // @public export const commandClass: (ccId: CommandClasses_2) => TypedClassDecorator_2; -// Warning: (ae-missing-release-tag) "CommandClassDeserializationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type CommandClassDeserializationOptions = { - data: Buffer; - origin?: MessageOrigin; - frameType?: FrameType; -} & ({ - fromEncapsulation?: false; - nodeId: number; -} | { - fromEncapsulation: true; - encapCC: CommandClass; -}); - -// Warning: (ae-forgotten-export) The symbol "CommandClassCreationOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "CommandClassOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type CommandClassOptions = CommandClassCreationOptions | CommandClassDeserializationOptions; +export interface CommandClassOptions extends CCAddress { + // (undocumented) + ccCommand?: number; + // (undocumented) + ccId?: number; + // (undocumented) + payload?: Buffer; +} // Warning: (ae-missing-release-tag) "ConfigurationCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -4312,25 +4590,25 @@ export type CommandClassOptions = CommandClassCreationOptions | CommandClassDese export class ConfigurationCC extends CommandClass { // (undocumented) ccCommand: ConfigurationCommand; - composePartialParamValue(applHost: ZWaveApplicationHost_2, parameter: number, bitMask: number, partialValue: number): number; - composePartialParamValues(applHost: ZWaveApplicationHost_2, parameter: number, partials: { + composePartialParamValue(ctx: GetValueDB_2, parameter: number, bitMask: number, partialValue: number): number; + composePartialParamValues(ctx: GetValueDB_2, parameter: number, partials: { bitMask: number; partialValue: number; }[]): number; - deserializeParamInformationFromConfig(applHost: ZWaveApplicationHost_2, config: ParamInfoMap): void; - getPartialParamInfos(applHost: ZWaveApplicationHost_2, parameter: number): (ValueID_2 & { + deserializeParamInformationFromConfig(ctx: GetValueDB_2 & GetDeviceConfig_2, config: ParamInfoMap): void; + getPartialParamInfos(ctx: GetValueDB_2, parameter: number): (ValueID_2 & { metadata: ConfigurationMetadata; })[]; - getQueriedParamInfos(applHost: ZWaveApplicationHost_2): Record; + getQueriedParamInfos(ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): Record; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; - protected paramExistsInConfigFile(applHost: ZWaveApplicationHost_2, parameter: number, valueBitMask?: number): boolean; + interview(ctx: InterviewContext): Promise; + protected paramExistsInConfigFile(ctx: GetValueDB_2 & GetDeviceConfig_2, parameter: number, valueBitMask?: number): boolean; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - translateProperty(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey?: string | number): string; + translateProperty(ctx: GetValueDB_2, property: string | number, propertyKey?: string | number): string; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey?: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey?: string | number): string | undefined; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration @@ -4356,19 +4634,21 @@ export type ConfigurationCCAPISetOptions = { // // @public (undocumented) export class ConfigurationCCBulkGet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCBulkGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ConfigurationCCBulkGet; // (undocumented) get parameters(): number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ConfigurationCCBulkGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCBulkGetOptions extends CCCommandOptions { +export interface ConfigurationCCBulkGetOptions { // (undocumented) parameters: number[]; } @@ -4377,32 +4657,52 @@ export interface ConfigurationCCBulkGetOptions extends CCCommandOptions { // // @public (undocumented) export class ConfigurationCCBulkReport extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - get defaultValues(): boolean; + defaultValues: boolean; // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCBulkReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) - get isHandshakeResponse(): boolean; + isHandshakeResponse: boolean; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - get reportsToFollow(): number; + reportsToFollow: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) get values(): ReadonlyMap; // (undocumented) - get valueSize(): number; + valueSize: number; +} + +// Warning: (ae-missing-release-tag) "ConfigurationCCBulkReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ConfigurationCCBulkReportOptions { + // (undocumented) + defaultValues: boolean; + // (undocumented) + isHandshakeResponse: boolean; + // (undocumented) + reportsToFollow: number; + // (undocumented) + values: Record; + // (undocumented) + valueSize: number; } // Warning: (ae-missing-release-tag) "ConfigurationCCBulkSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ConfigurationCCBulkSet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCBulkSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ConfigurationCCBulkSet; // (undocumented) get handshake(): boolean; // (undocumented) @@ -4410,9 +4710,9 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { // (undocumented) get resetToDefault(): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) get valueFormat(): ConfigValueFormat; // (undocumented) @@ -4424,7 +4724,7 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { // Warning: (ae-missing-release-tag) "ConfigurationCCBulkSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ConfigurationCCBulkSetOptions = CCCommandOptions & { +export type ConfigurationCCBulkSetOptions = { parameters: number[]; handshake?: boolean; } & ({ @@ -4446,21 +4746,23 @@ export class ConfigurationCCDefaultReset extends ConfigurationCC { // // @public (undocumented) export class ConfigurationCCGet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions); + constructor(options: WithAddress); // (undocumented) allowUnexpectedResponse: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCGet; + // (undocumented) parameter: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ConfigurationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCGetOptions extends CCCommandOptions { +export interface ConfigurationCCGetOptions { allowUnexpectedResponse?: boolean; // (undocumented) parameter: number; @@ -4470,45 +4772,49 @@ export interface ConfigurationCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class ConfigurationCCInfoGet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCInfoGet; // (undocumented) parameter: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ConfigurationCCInfoReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ConfigurationCCInfoReport extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCInfoReportOptions); + constructor(options: WithAddress); // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCInfoReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) info: string; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: ConfigurationCCInfoReport[]): void; + mergePartialCCs(partials: ConfigurationCCInfoReport[], _ctx: CCParsingContext_2): void; // (undocumented) readonly parameter: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "ConfigurationCCInfoReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCInfoReportOptions extends CCCommandOptions { +export interface ConfigurationCCInfoReportOptions { // (undocumented) info: string; // (undocumented) @@ -4521,45 +4827,49 @@ export interface ConfigurationCCInfoReportOptions extends CCCommandOptions { // // @public (undocumented) export class ConfigurationCCNameGet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCNameGet; // (undocumented) parameter: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ConfigurationCCNameReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ConfigurationCCNameReport extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCNameReportOptions); + constructor(options: WithAddress); // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCNameReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: ConfigurationCCNameReport[]): void; + mergePartialCCs(partials: ConfigurationCCNameReport[], _ctx: CCParsingContext_2): void; // (undocumented) name: string; // (undocumented) readonly parameter: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "ConfigurationCCNameReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCNameReportOptions extends CCCommandOptions { +export interface ConfigurationCCNameReportOptions { // (undocumented) name: string; // (undocumented) @@ -4572,25 +4882,29 @@ export interface ConfigurationCCNameReportOptions extends CCCommandOptions { // // @public (undocumented) export class ConfigurationCCPropertiesGet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCPropertiesGet; // (undocumented) parameter: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ConfigurationCCPropertiesReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ConfigurationCCPropertiesReport extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCPropertiesReportOptions); + constructor(options: WithAddress); // (undocumented) altersCapabilities: MaybeNotKnown; // (undocumented) defaultValue: MaybeNotKnown; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCPropertiesReport; + // (undocumented) isAdvanced: MaybeNotKnown; // (undocumented) isReadonly: MaybeNotKnown; @@ -4605,11 +4919,11 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { // (undocumented) parameter: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) valueFormat: ConfigValueFormat; // (undocumented) @@ -4620,7 +4934,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { // Warning: (ae-missing-release-tag) "ConfigurationCCPropertiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCPropertiesReportOptions extends CCCommandOptions { +export interface ConfigurationCCPropertiesReportOptions { // (undocumented) altersCapabilities?: boolean; // (undocumented) @@ -4649,15 +4963,17 @@ export interface ConfigurationCCPropertiesReportOptions extends CCCommandOptions // // @public (undocumented) export class ConfigurationCCReport extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCReport; // (undocumented) parameter: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) value: ConfigValue; // (undocumented) @@ -4668,7 +4984,7 @@ export class ConfigurationCCReport extends ConfigurationCC { // Warning: (ae-missing-release-tag) "ConfigurationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ConfigurationCCReportOptions extends CCCommandOptions { +export interface ConfigurationCCReportOptions { // (undocumented) parameter: number; // (undocumented) @@ -4683,15 +4999,17 @@ export interface ConfigurationCCReportOptions extends CCCommandOptions { // // @public (undocumented) export class ConfigurationCCSet extends ConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ConfigurationCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ConfigurationCCSet; // (undocumented) parameter: number; // (undocumented) resetToDefault: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) value: ConfigValue | undefined; // (undocumented) @@ -4703,7 +5021,7 @@ export class ConfigurationCCSet extends ConfigurationCC { // Warning: (ae-missing-release-tag) "ConfigurationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ConfigurationCCSetOptions = CCCommandOptions & ({ +export type ConfigurationCCSetOptions = { parameter: number; resetToDefault: true; } | { @@ -4712,18 +5030,13 @@ export type ConfigurationCCSetOptions = CCCommandOptions & ({ valueSize: number; valueFormat?: ConfigValueFormat; value: ConfigValue; -}); +}; // Warning: (ae-missing-release-tag) "ConfigurationCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const ConfigurationCCValues: Readonly<{ paramInformation: ((parameter: number, bitMask?: number | undefined) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Configuration; property: number; @@ -4735,6 +5048,11 @@ export const ConfigurationCCValues: Readonly<{ readonly property: number; readonly propertyKey: number | undefined; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -4763,10 +5081,10 @@ export const ConfigurationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -4808,7 +5126,7 @@ export enum ConfigurationCommand { // Warning: (ae-missing-release-tag) "configureLifelineAssociations" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function configureLifelineAssociations(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): Promise; +function configureLifelineAssociations(ctx: CCAPIHost>, endpoint: EndpointId_2 & SupportsCC & ControlsCC): Promise; export { ConfigValue } @@ -4818,7 +5136,7 @@ export { ConfigValue } export class CRC16CC extends CommandClass { // (undocumented) ccCommand: CRC16Command; - static encapsulate(host: ZWaveHost_2, cc: CommandClass): CRC16CCCommandEncapsulation; + static encapsulate(cc: CommandClass): CRC16CCCommandEncapsulation; static requiresEncapsulation(cc: CommandClass): boolean; } @@ -4826,21 +5144,23 @@ export class CRC16CC extends CommandClass { // // @public (undocumented) export class CRC16CCCommandEncapsulation extends CRC16CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CRC16CCCommandEncapsulationOptions); + constructor(options: WithAddress); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) encapsulated: CommandClass; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): CRC16CCCommandEncapsulation; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "CRC16CCCommandEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface CRC16CCCommandEncapsulationOptions extends CCCommandOptions { +export interface CRC16CCCommandEncapsulationOptions { // (undocumented) encapsulated: CommandClass; } @@ -4912,7 +5232,8 @@ export class DeviceResetLocallyCC extends CommandClass { // // @public (undocumented) export class DeviceResetLocallyCCNotification extends DeviceResetLocallyCC { - constructor(host: ZWaveHost_2, options: CommandClassOptions); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): DeviceResetLocallyCCNotification; } // Warning: (ae-missing-release-tag) "DeviceResetLocallyCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -4926,7 +5247,7 @@ export enum DeviceResetLocallyCommand { // Warning: (ae-missing-release-tag) "doesAnyLifelineSendActuatorOrSensorReports" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function doesAnyLifelineSendActuatorOrSensorReports(applHost: ZWaveApplicationHost_2, node: IZWaveNode): MaybeNotKnown; +function doesAnyLifelineSendActuatorOrSensorReports(ctx: GetValueDB & GetDeviceConfig, node: NodeId & SupportsCC): MaybeNotKnown; // Warning: (ae-missing-release-tag) "DoorHandleStatus" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -4940,9 +5261,9 @@ export class DoorLockCC extends CommandClass { // (undocumented) ccCommand: DoorLockCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "DoorLockCCCapabilitiesGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -4955,7 +5276,7 @@ export class DoorLockCCCapabilitiesGet extends DoorLockCC { // // @public (undocumented) export class DoorLockCCCapabilitiesReport extends DoorLockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly autoRelockSupported: boolean; // (undocumented) @@ -4965,6 +5286,8 @@ export class DoorLockCCCapabilitiesReport extends DoorLockCC { // (undocumented) readonly doorSupported: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): DoorLockCCCapabilitiesReport; + // (undocumented) readonly holdAndReleaseSupported: boolean; // (undocumented) readonly latchSupported: boolean; @@ -4977,11 +5300,39 @@ export class DoorLockCCCapabilitiesReport extends DoorLockCC { // (undocumented) readonly supportedOutsideHandles: DoorHandleStatus; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly twistAssistSupported: boolean; } +// Warning: (ae-missing-release-tag) "DoorLockCCCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DoorLockCCCapabilitiesReportOptions { + // (undocumented) + autoRelockSupported: boolean; + // (undocumented) + blockToBlockSupported: boolean; + // (undocumented) + boltSupported: boolean; + // (undocumented) + doorSupported: boolean; + // (undocumented) + holdAndReleaseSupported: boolean; + // (undocumented) + latchSupported: boolean; + // (undocumented) + supportedDoorLockModes: DoorLockMode[]; + // (undocumented) + supportedInsideHandles: DoorHandleStatus; + // (undocumented) + supportedOperationTypes: DoorLockOperationType[]; + // (undocumented) + supportedOutsideHandles: DoorHandleStatus; + // (undocumented) + twistAssistSupported: boolean; +} + // Warning: (ae-missing-release-tag) "DoorLockCCConfigurationGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -4992,12 +5343,14 @@ export class DoorLockCCConfigurationGet extends DoorLockCC { // // @public (undocumented) export class DoorLockCCConfigurationReport extends DoorLockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly autoRelockTime?: number; // (undocumented) readonly blockToBlock?: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): DoorLockCCConfigurationReport; + // (undocumented) readonly holdAndReleaseTime?: number; // (undocumented) readonly insideHandlesCanOpenDoorConfiguration: DoorHandleStatus; @@ -5008,23 +5361,47 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { // (undocumented) readonly outsideHandlesCanOpenDoorConfiguration: DoorHandleStatus; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly twistAssist?: boolean; } +// Warning: (ae-missing-release-tag) "DoorLockCCConfigurationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DoorLockCCConfigurationReportOptions { + // (undocumented) + autoRelockTime?: number; + // (undocumented) + blockToBlock?: boolean; + // (undocumented) + holdAndReleaseTime?: number; + // (undocumented) + insideHandlesCanOpenDoorConfiguration: DoorHandleStatus; + // (undocumented) + lockTimeoutConfiguration?: number; + // (undocumented) + operationType: DoorLockOperationType; + // (undocumented) + outsideHandlesCanOpenDoorConfiguration: DoorHandleStatus; + // (undocumented) + twistAssist?: boolean; +} + // Warning: (ae-missing-release-tag) "DoorLockCCConfigurationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class DoorLockCCConfigurationSet extends DoorLockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & DoorLockCCConfigurationSetOptions)); + constructor(options: WithAddress); // (undocumented) autoRelockTime?: number; // (undocumented) blockToBlock?: boolean; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): DoorLockCCConfigurationSet; + // (undocumented) holdAndReleaseTime?: number; // (undocumented) insideHandlesCanOpenDoorConfiguration: DoorHandleStatus; @@ -5035,9 +5412,9 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { // (undocumented) outsideHandlesCanOpenDoorConfiguration: DoorHandleStatus; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) twistAssist?: boolean; } @@ -5070,7 +5447,7 @@ export class DoorLockCCOperationGet extends DoorLockCC { // // @public (undocumented) export class DoorLockCCOperationReport extends DoorLockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly boltStatus?: "locked" | "unlocked"; // (undocumented) @@ -5080,6 +5457,8 @@ export class DoorLockCCOperationReport extends DoorLockCC { // (undocumented) readonly duration?: Duration; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): DoorLockCCOperationReport; + // (undocumented) readonly insideHandlesCanOpenDoor: DoorHandleStatus; // (undocumented) readonly latchStatus?: "open" | "closed"; @@ -5088,30 +5467,56 @@ export class DoorLockCCOperationReport extends DoorLockCC { // (undocumented) readonly outsideHandlesCanOpenDoor: DoorHandleStatus; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly targetMode?: DoorLockMode; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "DoorLockCCOperationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DoorLockCCOperationReportOptions { + // (undocumented) + boltStatus?: "unlocked" | "locked"; + // (undocumented) + currentMode: DoorLockMode; + // (undocumented) + doorStatus?: "closed" | "open"; + // (undocumented) + duration?: Duration; + // (undocumented) + insideHandlesCanOpenDoor: DoorHandleStatus; + // (undocumented) + latchStatus?: "closed" | "open"; + // (undocumented) + lockTimeout?: number; + // (undocumented) + outsideHandlesCanOpenDoor: DoorHandleStatus; + // (undocumented) + targetMode?: DoorLockMode; } // Warning: (ae-missing-release-tag) "DoorLockCCOperationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class DoorLockCCOperationSet extends DoorLockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | DoorLockCCOperationSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): DoorLockCCOperationSet; // (undocumented) mode: DoorLockMode; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "DoorLockCCOperationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface DoorLockCCOperationSetOptions extends CCCommandOptions { +export interface DoorLockCCOperationSetOptions { // (undocumented) mode: DoorLockMode; } @@ -5138,12 +5543,12 @@ export const DoorLockCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly supportsEndpoints: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; doorSupported: { @@ -5163,11 +5568,11 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -5189,12 +5594,12 @@ export const DoorLockCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly supportsEndpoints: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; boltSupported: { @@ -5214,11 +5619,11 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -5240,12 +5645,12 @@ export const DoorLockCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly supportsEndpoints: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; latchSupported: { @@ -5265,11 +5670,11 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -5291,12 +5696,12 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 4; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; blockToBlockSupported: { @@ -5316,10 +5721,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5342,12 +5747,12 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 4; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; twistAssistSupported: { @@ -5367,10 +5772,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5395,12 +5800,12 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 4; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; holdAndReleaseSupported: { @@ -5420,10 +5825,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5448,12 +5853,12 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 4; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; autoRelockSupported: { @@ -5473,10 +5878,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5637,10 +6042,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5714,10 +6119,10 @@ export const DoorLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: number; }; @@ -5740,11 +6145,11 @@ export const DoorLockCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; }; }; @@ -5841,28 +6246,30 @@ export class DoorLockLoggingCC extends CommandClass { // (undocumented) ccCommand: DoorLockLoggingCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "DoorLockLoggingCCRecordGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class DoorLockLoggingCCRecordGet extends DoorLockLoggingCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | DoorLockLoggingCCRecordGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): DoorLockLoggingCCRecordGet; // (undocumented) recordNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "DoorLockLoggingCCRecordGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface DoorLockLoggingCCRecordGetOptions extends CCCommandOptions { +export interface DoorLockLoggingCCRecordGetOptions { // (undocumented) recordNumber: number; } @@ -5871,13 +6278,25 @@ export interface DoorLockLoggingCCRecordGetOptions extends CCCommandOptions { // // @public (undocumented) export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): DoorLockLoggingCCRecordReport; // (undocumented) readonly record?: DoorLockLoggingRecord; // (undocumented) readonly recordNumber: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "DoorLockLoggingCCRecordReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DoorLockLoggingCCRecordReportOptions { + // (undocumented) + record?: DoorLockLoggingRecord; + // (undocumented) + recordNumber: number; } // Warning: (ae-missing-release-tag) "DoorLockLoggingCCRecordsSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -5890,11 +6309,21 @@ export class DoorLockLoggingCCRecordsSupportedGet extends DoorLockLoggingCC { // // @public (undocumented) export class DoorLockLoggingCCRecordsSupportedReport extends DoorLockLoggingCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): DoorLockLoggingCCRecordsSupportedReport; // (undocumented) readonly recordsCount: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "DoorLockLoggingCCRecordsSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DoorLockLoggingCCRecordsSupportedReportOptions { + // (undocumented) + recordsCount: number; } // Warning: (ae-missing-release-tag) "DoorLockLoggingCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -5918,11 +6347,11 @@ export const DoorLockLoggingCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -6096,20 +6525,9 @@ export type EncapsulatedCommandClass = CommandClass & { // // @public (undocumented) export type EncapsulatingCommandClass = CommandClass & { - constructor: EncapsulatingCommandClassStatic; encapsulated: EncapsulatedCommandClass; }; -// Warning: (ae-missing-release-tag) "EncapsulatingCommandClassStatic" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface EncapsulatingCommandClassStatic { - // (undocumented) - new (applHost: ZWaveApplicationHost, options: CommandClassOptions): EncapsulatingCommandClass; - // (undocumented) - encapsulate(applHost: ZWaveApplicationHost, cc: CommandClass): EncapsulatingCommandClass; -} - // Warning: (ae-missing-release-tag) "EndpointAddress" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -6127,28 +6545,30 @@ export class EnergyProductionCC extends CommandClass { // (undocumented) ccCommand: EnergyProductionCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "EnergyProductionCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class EnergyProductionCCGet extends EnergyProductionCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | EnergyProductionCCGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): EnergyProductionCCGet; // (undocumented) parameter: EnergyProductionParameter; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "EnergyProductionCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface EnergyProductionCCGetOptions extends CCCommandOptions { +export interface EnergyProductionCCGetOptions { // (undocumented) parameter: EnergyProductionParameter; } @@ -6157,17 +6577,19 @@ export interface EnergyProductionCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class EnergyProductionCCReport extends EnergyProductionCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | EnergyProductionCCReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): EnergyProductionCCReport; // (undocumented) readonly parameter: EnergyProductionParameter; // (undocumented) - persistValues(applHost: ZWaveApplicationHost): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly scale: EnergyProductionScale; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) readonly value: number; } @@ -6175,7 +6597,7 @@ export class EnergyProductionCCReport extends EnergyProductionCC { // Warning: (ae-missing-release-tag) "EnergyProductionCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface EnergyProductionCCReportOptions extends CCCommandOptions { +export interface EnergyProductionCCReportOptions { // (undocumented) parameter: EnergyProductionParameter; // (undocumented) @@ -6189,12 +6611,6 @@ export interface EnergyProductionCCReportOptions extends CCCommandOptions { // @public (undocumented) export const EnergyProductionCCValues: Readonly<{ value: ((parameter: EnergyProductionParameter) => { - readonly meta: { - readonly label: string; - readonly writeable: false; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Energy Production"]; property: "value"; @@ -6206,6 +6622,12 @@ export const EnergyProductionCCValues: Readonly<{ readonly property: "value"; readonly propertyKey: EnergyProductionParameter; }; + readonly meta: { + readonly label: string; + readonly writeable: false; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -6262,9 +6684,9 @@ export class EntryControlCC extends CommandClass { // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "EntryControlCCConfigurationGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6277,34 +6699,48 @@ export class EntryControlCCConfigurationGet extends EntryControlCC { // // @public (undocumented) export class EntryControlCCConfigurationReport extends EntryControlCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): EntryControlCCConfigurationReport; // (undocumented) readonly keyCacheSize: number; // (undocumented) readonly keyCacheTimeout: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "EntryControlCCConfigurationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntryControlCCConfigurationReportOptions { + // (undocumented) + keyCacheSize: number; + // (undocumented) + keyCacheTimeout: number; } // Warning: (ae-missing-release-tag) "EntryControlCCConfigurationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class EntryControlCCConfigurationSet extends EntryControlCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | EntryControlCCConfigurationSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): EntryControlCCConfigurationSet; // (undocumented) readonly keyCacheSize: number; // (undocumented) readonly keyCacheTimeout: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "EntryControlCCConfigurationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface EntryControlCCConfigurationSetOptions extends CCCommandOptions { +export interface EntryControlCCConfigurationSetOptions { // (undocumented) keyCacheSize: number; // (undocumented) @@ -6321,7 +6757,9 @@ export class EntryControlCCEventSupportedGet extends EntryControlCC { // // @public (undocumented) export class EntryControlCCEventSupportedReport extends EntryControlCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): EntryControlCCEventSupportedReport; // (undocumented) readonly maxKeyCacheSize: number; // (undocumented) @@ -6331,13 +6769,31 @@ export class EntryControlCCEventSupportedReport extends EntryControlCC { // (undocumented) readonly minKeyCacheTimeout: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly supportedDataTypes: readonly EntryControlDataTypes[]; // (undocumented) readonly supportedEventTypes: readonly EntryControlEventTypes[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "EntryControlCCEventSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntryControlCCEventSupportedReportOptions { + // (undocumented) + maxKeyCacheSize: number; + // (undocumented) + maxKeyCacheTimeout: number; + // (undocumented) + minKeyCacheSize: number; + // (undocumented) + minKeyCacheTimeout: number; + // (undocumented) + supportedDataTypes: EntryControlDataTypes[]; + // (undocumented) + supportedEventTypes: EntryControlEventTypes[]; } // Warning: (ae-missing-release-tag) "EntryControlCCKeySupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6350,18 +6806,28 @@ export class EntryControlCCKeySupportedGet extends EntryControlCC { // // @public (undocumented) export class EntryControlCCKeySupportedReport extends EntryControlCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): EntryControlCCKeySupportedReport; // (undocumented) readonly supportedKeys: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "EntryControlCCKeySupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntryControlCCKeySupportedReportOptions { + // (undocumented) + supportedKeys: number[]; } // Warning: (ae-missing-release-tag) "EntryControlCCNotification" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class EntryControlCCNotification extends EntryControlCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly dataType: EntryControlDataTypes; // (undocumented) @@ -6369,9 +6835,25 @@ export class EntryControlCCNotification extends EntryControlCC { // (undocumented) readonly eventType: EntryControlEventTypes; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): EntryControlCCNotification; + // (undocumented) readonly sequenceNumber: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "EntryControlCCNotificationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntryControlCCNotificationOptions { + // (undocumented) + dataType: EntryControlDataTypes; + // (undocumented) + eventData?: string | Buffer; + // (undocumented) + eventType: EntryControlEventTypes; + // (undocumented) + sequenceNumber: number; } // Warning: (ae-missing-release-tag) "EntryControlCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6395,11 +6877,11 @@ export const EntryControlCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -6420,11 +6902,11 @@ export const EntryControlCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -6445,11 +6927,11 @@ export const EntryControlCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -6629,17 +7111,19 @@ export function extensionType(type: S2ExtensionType): TypedClassDecorator; + static from(raw: CCRaw, ctx: CCParsingContext_2): FibaroCC; + // (undocumented) + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; } // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration @@ -6658,7 +7142,7 @@ export const fibaroCCCommand: (fibaroCCCommand: number // // @public (undocumented) export class FibaroVenetianBlindCC extends FibaroCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); + constructor(options: CommandClassOptions); // Warning: (ae-forgotten-export) The symbol "FibaroVenetianBlindCCCommand" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -6668,59 +7152,75 @@ export class FibaroVenetianBlindCC extends FibaroCC { // (undocumented) fibaroCCId: FibaroCCIDs.VenetianBlind; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "FibaroVenetianBlindCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FibaroVenetianBlindCCGet extends FibaroVenetianBlindCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); + constructor(options: CommandClassOptions); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FibaroVenetianBlindCCGet; } // Warning: (ae-missing-release-tag) "FibaroVenetianBlindCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FibaroVenetianBlindCCReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - get position(): MaybeUnknown | undefined; + position: MaybeUnknown | undefined; + // (undocumented) + tilt: MaybeUnknown | undefined; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "FibaroVenetianBlindCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FibaroVenetianBlindCCReportOptions { // (undocumented) - get tilt(): MaybeUnknown | undefined; + position?: MaybeUnknown; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + tilt?: MaybeUnknown; } // Warning: (ae-missing-release-tag) "FibaroVenetianBlindCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | FibaroVenetianBlindCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): FibaroVenetianBlindCCSet; // (undocumented) position: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) tilt: number | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FibaroVenetianBlindCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type FibaroVenetianBlindCCSetOptions = CCCommandOptions & ({ +export type FibaroVenetianBlindCCSetOptions = { position: number; } | { tilt: number; } | { position: number; tilt: number; -}); +}; // Warning: (ae-missing-release-tag) "FirmwareDownloadStatus" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -6811,7 +7311,7 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { // (undocumented) ccCommand: FirmwareUpdateMetaDataCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) skipEndpointInterview(): boolean; } @@ -6820,7 +7320,7 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { // // @public (undocumented) export class FirmwareUpdateMetaDataCCActivationReport extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly activationStatus: FirmwareUpdateActivationStatus; // (undocumented) @@ -6830,18 +7330,38 @@ export class FirmwareUpdateMetaDataCCActivationReport extends FirmwareUpdateMeta // (undocumented) readonly firmwareTarget: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCActivationReport; + // (undocumented) readonly hardwareVersion?: number; // (undocumented) readonly manufacturerId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCActivationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FirmwareUpdateMetaDataCCActivationReportOptions { + // (undocumented) + activationStatus: FirmwareUpdateActivationStatus; + // (undocumented) + checksum: number; + // (undocumented) + firmwareId: number; + // (undocumented) + firmwareTarget: number; + // (undocumented) + hardwareVersion?: number; + // (undocumented) + manufacturerId: number; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCActivationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FirmwareUpdateMetaDataCCActivationSet extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (FirmwareUpdateMetaDataCCActivationSetOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) checksum: number; // (undocumented) @@ -6849,13 +7369,15 @@ export class FirmwareUpdateMetaDataCCActivationSet extends FirmwareUpdateMetaDat // (undocumented) firmwareTarget: number; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCActivationSet; + // (undocumented) hardwareVersion?: number; // (undocumented) manufacturerId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCActivationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6878,13 +7400,25 @@ export interface FirmwareUpdateMetaDataCCActivationSetOptions { // // @public (undocumented) export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCGet; // (undocumented) readonly numReports: number; // (undocumented) readonly reportNumber: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FirmwareUpdateMetaDataCCGetOptions { + // (undocumented) + numReports: number; + // (undocumented) + reportNumber: number; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCMetaDataGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6897,7 +7431,7 @@ export class FirmwareUpdateMetaDataCCMetaDataGet extends FirmwareUpdateMetaDataC // // @public (undocumented) export class FirmwareUpdateMetaDataCCMetaDataReport extends FirmwareUpdateMetaDataCC implements FirmwareUpdateMetaData { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (FirmwareUpdateMetaDataCCMetaDataReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) readonly additionalFirmwareIDs: readonly number[]; // (undocumented) @@ -6909,13 +7443,15 @@ export class FirmwareUpdateMetaDataCCMetaDataReport extends FirmwareUpdateMetaDa // (undocumented) readonly firmwareUpgradable: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCMetaDataReport; + // (undocumented) readonly hardwareVersion?: number; // (undocumented) readonly manufacturerId: number; // (undocumented) readonly maxFragmentSize?: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportsActivation: MaybeNotKnown; // (undocumented) @@ -6923,7 +7459,7 @@ export class FirmwareUpdateMetaDataCCMetaDataReport extends FirmwareUpdateMetaDa // (undocumented) readonly supportsResuming?: MaybeNotKnown; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCMetaDataReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -6958,7 +7494,7 @@ export interface FirmwareUpdateMetaDataCCMetaDataReportOptions { // // @public (undocumented) export class FirmwareUpdateMetaDataCCPrepareGet extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | FirmwareUpdateMetaDataCCPrepareGetOptions); + constructor(options: WithAddress); // (undocumented) firmwareId: number; // (undocumented) @@ -6966,19 +7502,21 @@ export class FirmwareUpdateMetaDataCCPrepareGet extends FirmwareUpdateMetaDataCC // (undocumented) fragmentSize: number; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCPrepareGet; + // (undocumented) hardwareVersion: number; // (undocumented) manufacturerId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCPrepareGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface FirmwareUpdateMetaDataCCPrepareGetOptions extends CCCommandOptions { +export interface FirmwareUpdateMetaDataCCPrepareGetOptions { // (undocumented) firmwareId: number; // (undocumented) @@ -6995,36 +7533,50 @@ export interface FirmwareUpdateMetaDataCCPrepareGetOptions extends CCCommandOpti // // @public (undocumented) export class FirmwareUpdateMetaDataCCPrepareReport extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly checksum: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCPrepareReport; + // (undocumented) readonly status: FirmwareDownloadStatus; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCPrepareReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FirmwareUpdateMetaDataCCPrepareReportOptions { + // (undocumented) + checksum: number; + // (undocumented) + status: FirmwareDownloadStatus; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | FirmwareUpdateMetaDataCCReportOptions); + constructor(options: WithAddress); // (undocumented) firmwareData: Buffer; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCReport; + // (undocumented) isLast: boolean; // (undocumented) reportNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface FirmwareUpdateMetaDataCCReportOptions extends CCCommandOptions { +export interface FirmwareUpdateMetaDataCCReportOptions { // (undocumented) firmwareData: Buffer; // (undocumented) @@ -7037,7 +7589,7 @@ export interface FirmwareUpdateMetaDataCCReportOptions extends CCCommandOptions // // @public (undocumented) export class FirmwareUpdateMetaDataCCRequestGet extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (FirmwareUpdateMetaDataCCRequestGetOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) activation?: boolean; // (undocumented) @@ -7049,6 +7601,8 @@ export class FirmwareUpdateMetaDataCCRequestGet extends FirmwareUpdateMetaDataCC // (undocumented) fragmentSize?: number; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCRequestGet; + // (undocumented) hardwareVersion?: number; // (undocumented) manufacturerId: number; @@ -7057,9 +7611,9 @@ export class FirmwareUpdateMetaDataCCRequestGet extends FirmwareUpdateMetaDataCC // (undocumented) resume?: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCRequestGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7082,7 +7636,9 @@ export type FirmwareUpdateMetaDataCCRequestGetOptions = { // // @public (undocumented) export class FirmwareUpdateMetaDataCCRequestReport extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCRequestReport; // (undocumented) nonSecureTransfer?: boolean; // (undocumented) @@ -7090,21 +7646,45 @@ export class FirmwareUpdateMetaDataCCRequestReport extends FirmwareUpdateMetaDat // (undocumented) readonly status: FirmwareUpdateRequestStatus; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCRequestReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FirmwareUpdateMetaDataCCRequestReportOptions { + // (undocumented) + nonSecureTransfer?: boolean; + // (undocumented) + resume?: boolean; + // (undocumented) + status: FirmwareUpdateRequestStatus; } // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCStatusReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class FirmwareUpdateMetaDataCCStatusReport extends FirmwareUpdateMetaDataCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): FirmwareUpdateMetaDataCCStatusReport; // (undocumented) readonly status: FirmwareUpdateStatus; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; readonly waitTime?: number; } +// Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCStatusReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface FirmwareUpdateMetaDataCCStatusReportOptions { + // (undocumented) + status: FirmwareUpdateStatus; + // (undocumented) + waitTime?: number; +} + // Warning: (ae-missing-release-tag) "FirmwareUpdateMetaDataCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -7126,11 +7706,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7151,11 +7731,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7176,11 +7756,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7201,11 +7781,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7226,11 +7806,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7251,11 +7831,11 @@ export const FirmwareUpdateMetaDataCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7379,12 +7959,12 @@ export function FLiRS2WakeUpTime(value: FLiRS_2): WakeUpTime; // Warning: (ae-missing-release-tag) "getAllAssociationGroups" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function getAllAssociationGroups(applHost: ZWaveApplicationHost_2, node: IZWaveNode): ReadonlyMap>; +function getAllAssociationGroups(ctx: GetValueDB & GetDeviceConfig, node: NodeId & GetAllEndpoints): ReadonlyMap>; // Warning: (ae-missing-release-tag) "getAllAssociations" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function getAllAssociations(applHost: ZWaveApplicationHost_2, node: IZWaveNode): ReadonlyObjectKeyMap>; +function getAllAssociations(ctx: GetValueDB, node: NodeId & GetAllEndpoints): ReadonlyObjectKeyMap>; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "getAPI" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7395,12 +7975,12 @@ export function getAPI(cc: CommandClasses_2): APIConstructor | undefined; // Warning: (ae-missing-release-tag) "getAssociationGroups" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function getAssociationGroups(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): ReadonlyMap; +function getAssociationGroups(ctx: GetValueDB & GetDeviceConfig, endpoint: EndpointId_2 & SupportsCC): ReadonlyMap; // Warning: (ae-missing-release-tag) "getAssociations" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function getAssociations(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): ReadonlyMap; +function getAssociations(ctx: GetValueDB, endpoint: EndpointId_2 & SupportsCC): ReadonlyMap; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "getCCCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7451,6 +8031,11 @@ export function getCommandClass(cc: CommandClass | CCAPI): CommandClasses_2; // @public export function getCommandClassStatic>(classConstructor: T): CommandClasses_2; +// Warning: (ae-missing-release-tag) "getEffectiveCCVersion" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getEffectiveCCVersion(ctx: GetSupportedCCVersion, cc: CCId, defaultVersion?: number): number; + // Warning: (ae-missing-release-tag) "getEnergyProductionScale" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -7512,7 +8097,7 @@ export function getInnermostCommandClass(cc: CommandClass): CommandClass; // Warning: (ae-missing-release-tag) "getLifelineGroupIds" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function getLifelineGroupIds(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): number[]; +function getLifelineGroupIds(ctx: GetValueDB & GetDeviceConfig, endpoint: EndpointId_2 & SupportsCC): number[]; // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "getManufacturerId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7545,11 +8130,6 @@ export const getManufacturerProprietaryCCConstructor: (manufacturerId: number) = // @public export function getS2ExtensionConstructor(type: S2ExtensionType): Security2ExtensionConstructor | undefined; -// Warning: (ae-missing-release-tag) "gotDeserializationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function gotDeserializationOptions(options: CommandClassOptions): options is CommandClassDeserializationOptions; - // Warning: (ae-missing-release-tag) "HailCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -7587,9 +8167,9 @@ export class HumidityControlModeCC extends CommandClass { // (undocumented) ccCommand: HumidityControlModeCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "HumidityControlModeCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7602,30 +8182,42 @@ export class HumidityControlModeCCGet extends HumidityControlModeCC { // // @public (undocumented) export class HumidityControlModeCCReport extends HumidityControlModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlModeCCReport; // (undocumented) readonly mode: HumidityControlMode; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "HumidityControlModeCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlModeCCReportOptions { + // (undocumented) + mode: HumidityControlMode; } // Warning: (ae-missing-release-tag) "HumidityControlModeCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class HumidityControlModeCCSet extends HumidityControlModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | HumidityControlModeCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): HumidityControlModeCCSet; // (undocumented) mode: HumidityControlMode; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "HumidityControlModeCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface HumidityControlModeCCSetOptions extends CCCommandOptions { +export interface HumidityControlModeCCSetOptions { // (undocumented) mode: HumidityControlMode; } @@ -7640,13 +8232,23 @@ export class HumidityControlModeCCSupportedGet extends HumidityControlModeCC { // // @public (undocumented) export class HumidityControlModeCCSupportedReport extends HumidityControlModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlModeCCSupportedReport; // (undocumented) - get supportedModes(): readonly HumidityControlMode[]; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + supportedModes: HumidityControlMode[]; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "HumidityControlModeCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlModeCCSupportedReportOptions { // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + supportedModes: HumidityControlMode[]; } // Warning: (ae-missing-release-tag) "HumidityControlModeCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7670,11 +8272,11 @@ export const HumidityControlModeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -7746,9 +8348,9 @@ export class HumidityControlOperatingStateCC extends CommandClass { // (undocumented) ccCommand: HumidityControlOperatingStateCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "HumidityControlOperatingStateCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7761,11 +8363,21 @@ export class HumidityControlOperatingStateCCGet extends HumidityControlOperating // // @public (undocumented) export class HumidityControlOperatingStateCCReport extends HumidityControlOperatingStateCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlOperatingStateCCReport; // (undocumented) readonly state: HumidityControlOperatingState; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "HumidityControlOperatingStateCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlOperatingStateCCReportOptions { + // (undocumented) + state: HumidityControlOperatingState; } // Warning: (ae-missing-release-tag) "HumidityControlOperatingStateCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -7836,30 +8448,32 @@ export class HumidityControlSetpointCC extends CommandClass { // (undocumented) ccCommand: HumidityControlSetpointCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCCapabilitiesGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class HumidityControlSetpointCCCapabilitiesGet extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | HumidityControlSetpointCCCapabilitiesGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): HumidityControlSetpointCCCapabilitiesGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: HumidityControlSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCCapabilitiesGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface HumidityControlSetpointCCCapabilitiesGetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCCapabilitiesGetOptions { // (undocumented) setpointType: HumidityControlSetpointType; } @@ -7868,40 +8482,60 @@ export interface HumidityControlSetpointCCCapabilitiesGetOptions extends CCComma // // @public (undocumented) export class HumidityControlSetpointCCCapabilitiesReport extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlSetpointCCCapabilitiesReport; + // (undocumented) + maxValue: number; // (undocumented) - get maxValue(): number; + maxValueScale: number; + // (undocumented) + minValue: number; + // (undocumented) + minValueScale: number; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - get maxValueScale(): number; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; + // (undocumented) + type: HumidityControlSetpointType; +} + +// Warning: (ae-missing-release-tag) "HumidityControlSetpointCCCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlSetpointCCCapabilitiesReportOptions { // (undocumented) - get minValue(): number; + maxValue: number; // (undocumented) - get minValueScale(): number; + maxValueScale: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + minValue: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + minValueScale: number; // (undocumented) - get type(): HumidityControlSetpointType; + type: HumidityControlSetpointType; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | HumidityControlSetpointCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): HumidityControlSetpointCCGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: HumidityControlSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface HumidityControlSetpointCCGetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCGetOptions { // (undocumented) setpointType: HumidityControlSetpointType; } @@ -7910,36 +8544,52 @@ export interface HumidityControlSetpointCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlSetpointCCReport; // (undocumented) - readonly scale: number; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + scale: number; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; + // (undocumented) + type: HumidityControlSetpointType; + // (undocumented) + value: number; +} + +// Warning: (ae-missing-release-tag) "HumidityControlSetpointCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlSetpointCCReportOptions { + // (undocumented) + scale: number; // (undocumented) - get type(): HumidityControlSetpointType; + type: HumidityControlSetpointType; // (undocumented) - get value(): number; + value: number; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCScaleSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class HumidityControlSetpointCCScaleSupportedGet extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | HumidityControlSetpointCCScaleSupportedGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): HumidityControlSetpointCCScaleSupportedGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: HumidityControlSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCScaleSupportedGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface HumidityControlSetpointCCScaleSupportedGetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCScaleSupportedGetOptions { // (undocumented) setpointType: HumidityControlSetpointType; } @@ -7948,26 +8598,38 @@ export interface HumidityControlSetpointCCScaleSupportedGetOptions extends CCCom // // @public (undocumented) export class HumidityControlSetpointCCScaleSupportedReport extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlSetpointCCScaleSupportedReport; // (undocumented) readonly supportedScales: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "HumidityControlSetpointCCScaleSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlSetpointCCScaleSupportedReportOptions { + // (undocumented) + supportedScales: number[]; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | HumidityControlSetpointCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): HumidityControlSetpointCCSet; // (undocumented) scale: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: HumidityControlSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) value: number; } @@ -7975,7 +8637,7 @@ export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface HumidityControlSetpointCCSetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCSetOptions { // (undocumented) scale: number; // (undocumented) @@ -7994,11 +8656,21 @@ export class HumidityControlSetpointCCSupportedGet extends HumidityControlSetpoi // // @public (undocumented) export class HumidityControlSetpointCCSupportedReport extends HumidityControlSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): HumidityControlSetpointCCSupportedReport; // (undocumented) readonly supportedSetpointTypes: readonly HumidityControlSetpointType[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "HumidityControlSetpointCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface HumidityControlSetpointCCSupportedReportOptions { + // (undocumented) + supportedSetpointTypes: HumidityControlSetpointType[]; } // Warning: (ae-missing-release-tag) "HumidityControlSetpointCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -8006,14 +8678,6 @@ export class HumidityControlSetpointCCSupportedReport extends HumidityControlSet // @public (undocumented) export const HumidityControlSetpointCCValues: Readonly<{ setpointScale: ((setpointType: number) => { - readonly meta: { - readonly label: `Setpoint scale (${string})`; - readonly writeable: false; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Humidity Control Setpoint"]; property: "setpointScale"; @@ -8025,6 +8689,14 @@ export const HumidityControlSetpointCCValues: Readonly<{ readonly property: "setpointScale"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Setpoint scale (${string})`; + readonly writeable: false; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -8037,15 +8709,6 @@ export const HumidityControlSetpointCCValues: Readonly<{ }; }; setpoint: ((setpointType: number) => { - readonly meta: { - readonly label: `Setpoint (${string})`; - readonly ccSpecific: { - readonly setpointType: number; - }; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Humidity Control Setpoint"]; property: "setpoint"; @@ -8057,6 +8720,15 @@ export const HumidityControlSetpointCCValues: Readonly<{ readonly property: "setpoint"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Setpoint (${string})`; + readonly ccSpecific: { + readonly setpointType: number; + }; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -8085,11 +8757,11 @@ export const HumidityControlSetpointCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -8152,14 +8824,6 @@ export interface HumidityControlSetpointValue { value: number; } -// Warning: (ae-missing-release-tag) "ICommandClassContainer" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface ICommandClassContainer { - // (undocumented) - command: CommandClass; -} - // Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration // Warning: (ae-missing-release-tag) "implementedVersion" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -8178,21 +8842,23 @@ export class InclusionControllerCC extends CommandClass { // // @public (undocumented) export class InclusionControllerCCComplete extends InclusionControllerCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | InclusionControllerCCCompleteOptions); + constructor(options: WithAddress_2); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): InclusionControllerCCComplete; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) status: InclusionControllerStatus; // (undocumented) step: InclusionControllerStep; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "InclusionControllerCCCompleteOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface InclusionControllerCCCompleteOptions extends CCCommandOptions { +export interface InclusionControllerCCCompleteOptions { // (undocumented) status: InclusionControllerStatus; // (undocumented) @@ -8203,21 +8869,23 @@ export interface InclusionControllerCCCompleteOptions extends CCCommandOptions { // // @public (undocumented) export class InclusionControllerCCInitiate extends InclusionControllerCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | InclusionControllerCCInitiateOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): InclusionControllerCCInitiate; // (undocumented) includedNodeId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) step: InclusionControllerStep; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "InclusionControllerCCInitiateOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface InclusionControllerCCInitiateOptions extends CCCommandOptions { +export interface InclusionControllerCCInitiateOptions { // (undocumented) includedNodeId: number; // (undocumented) @@ -8287,36 +8955,38 @@ export class IndicatorCC extends CommandClass { // (undocumented) ccCommand: IndicatorCommand; // (undocumented) - static getSupportedPropertyIDsCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, indicatorId: number): MaybeNotKnown; + static getSupportedPropertyIDsCached(ctx: GetValueDB_2, endpoint: EndpointId_2, indicatorId: number): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - protected supportsV2Indicators(applHost: ZWaveApplicationHost_2): boolean; + protected supportsV2Indicators(ctx: GetValueDB_2): boolean; // (undocumented) - translateProperty(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey?: string | number): string; + translateProperty(ctx: GetValueDB_2, property: string | number, propertyKey?: string | number): string; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "IndicatorCCDescriptionGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class IndicatorCCDescriptionGet extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IndicatorCCDescriptionGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCDescriptionGet; // (undocumented) indicatorId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IndicatorCCDescriptionGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IndicatorCCDescriptionGetOptions extends CCCommandOptions { +export interface IndicatorCCDescriptionGetOptions { // (undocumented) indicatorId: number; } @@ -8325,17 +8995,19 @@ export interface IndicatorCCDescriptionGetOptions extends CCCommandOptions { // // @public (undocumented) export class IndicatorCCDescriptionReport extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (IndicatorCCDescriptionReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) description: string; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCDescriptionReport; + // (undocumented) indicatorId: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IndicatorCCDescriptionReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -8352,19 +9024,21 @@ export interface IndicatorCCDescriptionReportOptions { // // @public (undocumented) export class IndicatorCCGet extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IndicatorCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCGet; // (undocumented) indicatorId: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IndicatorCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IndicatorCCGetOptions extends CCCommandOptions { +export interface IndicatorCCGetOptions { // (undocumented) indicatorId?: number; } @@ -8373,25 +9047,25 @@ export interface IndicatorCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class IndicatorCCReport extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (IndicatorCCReportSpecificOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCReport; // (undocumented) readonly indicator0Value: number | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; - // Warning: (ae-forgotten-export) The symbol "IndicatorObject" needs to be exported by the entry point index.d.ts - // + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly values: IndicatorObject[] | undefined; } -// Warning: (ae-missing-release-tag) "IndicatorCCReportSpecificOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "IndicatorCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type IndicatorCCReportSpecificOptions = { +export type IndicatorCCReportOptions = { value: number; } | { values: IndicatorObject[]; @@ -8401,13 +9075,15 @@ export type IndicatorCCReportSpecificOptions = { // // @public (undocumented) export class IndicatorCCSet extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (IndicatorCCSetOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCSet; // (undocumented) indicator0Value: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) values: IndicatorObject[] | undefined; } @@ -8425,19 +9101,21 @@ export type IndicatorCCSetOptions = { // // @public (undocumented) export class IndicatorCCSupportedGet extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IndicatorCCSupportedGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCSupportedGet; // (undocumented) indicatorId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IndicatorCCSupportedGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IndicatorCCSupportedGetOptions extends CCCommandOptions { +export interface IndicatorCCSupportedGetOptions { // (undocumented) indicatorId: number; } @@ -8446,25 +9124,27 @@ export interface IndicatorCCSupportedGetOptions extends CCCommandOptions { // // @public (undocumented) export class IndicatorCCSupportedReport extends IndicatorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IndicatorCCSupportedReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IndicatorCCSupportedReport; // (undocumented) readonly indicatorId: number; // (undocumented) readonly nextIndicatorId: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedProperties: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IndicatorCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IndicatorCCSupportedReportOptions extends CCCommandOptions { +export interface IndicatorCCSupportedReportOptions { // (undocumented) indicatorId: number; // (undocumented) @@ -8478,11 +9158,6 @@ export interface IndicatorCCSupportedReportOptions extends CCCommandOptions { // @public (undocumented) export const IndicatorCCValues: Readonly<{ indicatorDescription: ((indicatorId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Indicator; property: number; @@ -8492,27 +9167,23 @@ export const IndicatorCCValues: Readonly<{ readonly endpoint: number; readonly property: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly internal: true; readonly minVersion: 4; }; }; valueV2: ((indicatorId: number, propertyId: number) => { - readonly meta: { - readonly ccSpecific: { - indicatorId: number; - propertyId: number; - }; - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Indicator; property: number; @@ -8524,23 +9195,27 @@ export const IndicatorCCValues: Readonly<{ readonly property: number; readonly propertyKey: number; }; + readonly meta: { + readonly ccSpecific: { + indicatorId: number; + propertyId: number; + }; + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; supportedPropertyIDs: ((indicatorId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Indicator; property: "supportedPropertyIDs"; @@ -8552,14 +9227,19 @@ export const IndicatorCCValues: Readonly<{ readonly property: "supportedPropertyIDs"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -8581,11 +9261,11 @@ export const IndicatorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; }; }; @@ -8610,11 +9290,11 @@ export const IndicatorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; }; }; @@ -8666,11 +9346,11 @@ export const IndicatorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -8706,6 +9386,18 @@ export type IndicatorMetadata = ValueMetadata_2 & { }; }; +// Warning: (ae-missing-release-tag) "IndicatorObject" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IndicatorObject { + // (undocumented) + indicatorId: number; + // (undocumented) + propertyId: number; + // (undocumented) + value: number | boolean; +} + // Warning: (ae-missing-release-tag) "IndicatorTimeout" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -8715,23 +9407,28 @@ export interface IndicatorTimeout { seconds?: number; } +// Warning: (ae-missing-release-tag) "InterviewContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type InterviewContext = CCAPIHost & GetAllEndpoints_2> & GetInterviewOptions & LookupManufacturer; + // Warning: (ae-missing-release-tag) "InvalidCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class InvalidCC extends CommandClass { - constructor(host: ZWaveHost, options: InvalidCCCreationOptions); + constructor(options: InvalidCCOptions); // (undocumented) get ccName(): string; // (undocumented) readonly reason?: string | ZWaveErrorCodes; // (undocumented) - toLogEntry(_host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(_ctx?: GetValueDB): MessageOrCCLogEntry; } -// Warning: (ae-missing-release-tag) "InvalidCCCreationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "InvalidCCOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface InvalidCCCreationOptions extends CommandClassCreationOptions { +export interface InvalidCCOptions extends CommandClassOptions { // (undocumented) ccName: string; // (undocumented) @@ -8744,15 +9441,15 @@ export interface InvalidCCCreationOptions extends CommandClassCreationOptions { export class IrrigationCC extends CommandClass { // (undocumented) ccCommand: IrrigationCommand; - static getMaxValveTableSizeCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; - static getNumValvesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; + static getMaxValveTableSizeCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getNumValvesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; - static supportsMasterValveCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): boolean; + refreshValues(ctx: RefreshValuesContext): Promise; + static supportsMasterValveCached(ctx: GetValueDB_2, endpoint: EndpointId_2): boolean; // (undocumented) - translateProperty(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey?: string | number): string; + translateProperty(ctx: GetValueDB_2, property: string | number, propertyKey?: string | number): string; } // Warning: (ae-missing-release-tag) "IrrigationCCSystemConfigGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -8765,7 +9462,9 @@ export class IrrigationCCSystemConfigGet extends IrrigationCC { // // @public (undocumented) export class IrrigationCCSystemConfigReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCSystemConfigReport; // (undocumented) readonly highPressureThreshold: number; // (undocumented) @@ -8777,14 +9476,32 @@ export class IrrigationCCSystemConfigReport extends IrrigationCC { // (undocumented) readonly rainSensorPolarity?: IrrigationSensorPolarity; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "IrrigationCCSystemConfigReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCSystemConfigReportOptions { + // (undocumented) + highPressureThreshold: number; + // (undocumented) + lowPressureThreshold: number; + // (undocumented) + masterValveDelay: number; + // (undocumented) + moistureSensorPolarity?: IrrigationSensorPolarity; + // (undocumented) + rainSensorPolarity?: IrrigationSensorPolarity; } // Warning: (ae-missing-release-tag) "IrrigationCCSystemConfigSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class IrrigationCCSystemConfigSet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (IrrigationCCSystemConfigSetOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCSystemConfigSet; // (undocumented) highPressureThreshold: number; // (undocumented) @@ -8796,9 +9513,9 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { // (undocumented) rainSensorPolarity?: IrrigationSensorPolarity; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IrrigationCCSystemConfigSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -8822,7 +9539,9 @@ export class IrrigationCCSystemInfoGet extends IrrigationCC { // // @public (undocumented) export class IrrigationCCSystemInfoReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCSystemInfoReport; // (undocumented) readonly maxValveTableSize: number; // (undocumented) @@ -8832,26 +9551,42 @@ export class IrrigationCCSystemInfoReport extends IrrigationCC { // (undocumented) readonly supportsMasterValve: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "IrrigationCCSystemInfoReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCSystemInfoReportOptions { + // (undocumented) + maxValveTableSize: number; + // (undocumented) + numValves: number; + // (undocumented) + numValveTables: number; + // (undocumented) + supportsMasterValve: boolean; } // Warning: (ae-missing-release-tag) "IrrigationCCSystemShutoff" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class IrrigationCCSystemShutoff extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCSystemShutoffOptions); + constructor(options: WithAddress); // (undocumented) duration?: number; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCSystemShutoff; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IrrigationCCSystemShutoffOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCSystemShutoffOptions extends CCCommandOptions { +export interface IrrigationCCSystemShutoffOptions { duration?: number; } @@ -8865,7 +9600,7 @@ export class IrrigationCCSystemStatusGet extends IrrigationCC { // // @public (undocumented) export class IrrigationCCSystemStatusReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) errorEmergencyShutdown: boolean; // (undocumented) @@ -8883,6 +9618,8 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { // (undocumented) flowSensorActive: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCSystemStatusReport; + // (undocumented) masterValveOpen: boolean; // (undocumented) moistureSensorActive: boolean; @@ -8897,7 +9634,43 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { // (undocumented) systemVoltage: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "IrrigationCCSystemStatusReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCSystemStatusReportOptions { + // (undocumented) + errorEmergencyShutdown: boolean; + // (undocumented) + errorHighPressure: boolean; + // (undocumented) + errorLowPressure: boolean; + // (undocumented) + errorNotProgrammed: boolean; + // (undocumented) + errorValve: boolean; + // (undocumented) + firstOpenZoneId?: number; + // (undocumented) + flow?: number; + // (undocumented) + flowSensorActive: boolean; + // (undocumented) + masterValveOpen: boolean; + // (undocumented) + moistureSensorActive: boolean; + // (undocumented) + pressure?: number; + // (undocumented) + pressureSensorActive: boolean; + // (undocumented) + rainSensorActive: boolean; + // (undocumented) + shutoffDuration: number; + // (undocumented) + systemVoltage: number; } // Warning: (ae-missing-release-tag) "IrrigationCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -8905,16 +9678,6 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { // @public (undocumented) export const IrrigationCCValues: Readonly<{ valveRunStartStop: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Start/Stop`; - readonly states: { - readonly true: "Start"; - readonly false: "Stop"; - }; - readonly type: "boolean"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -8926,6 +9689,16 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "startStop"; }; + readonly meta: { + readonly label: `${string}: Start/Stop`; + readonly states: { + readonly true: "Start"; + readonly false: "Stop"; + }; + readonly type: "boolean"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -8938,15 +9711,6 @@ export const IrrigationCCValues: Readonly<{ }; }; valveRunDuration: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Run duration`; - readonly min: 1; - readonly unit: "s"; - readonly max: 65535; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -8958,6 +9722,15 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "duration"; }; + readonly meta: { + readonly label: `${string}: Run duration`; + readonly min: 1; + readonly unit: "s"; + readonly max: 65535; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -8970,12 +9743,6 @@ export const IrrigationCCValues: Readonly<{ }; }; useMoistureSensor: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Use moisture sensor`; - readonly type: "boolean"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -8987,6 +9754,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "useMoistureSensor"; }; + readonly meta: { + readonly label: `${string}: Use moisture sensor`; + readonly type: "boolean"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -8999,12 +9772,6 @@ export const IrrigationCCValues: Readonly<{ }; }; useRainSensor: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Use rain sensor`; - readonly type: "boolean"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9016,6 +9783,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "useRainSensor"; }; + readonly meta: { + readonly label: `${string}: Use rain sensor`; + readonly type: "boolean"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9028,12 +9801,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorLowFlow: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Flow below high threshold`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9045,6 +9812,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorLowFlow"; }; + readonly meta: { + readonly label: `${string}: Error - Flow below high threshold`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9057,14 +9830,6 @@ export const IrrigationCCValues: Readonly<{ }; }; lowFlowThreshold: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Low flow threshold`; - readonly min: 0; - readonly unit: "l/h"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9076,6 +9841,14 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "lowFlowThreshold"; }; + readonly meta: { + readonly label: `${string}: Low flow threshold`; + readonly min: 0; + readonly unit: "l/h"; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9088,12 +9861,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorHighFlow: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Flow above high threshold`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9105,6 +9872,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorHighFlow"; }; + readonly meta: { + readonly label: `${string}: Error - Flow above high threshold`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9117,14 +9890,6 @@ export const IrrigationCCValues: Readonly<{ }; }; highFlowThreshold: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: High flow threshold`; - readonly min: 0; - readonly unit: "l/h"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9136,6 +9901,14 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "highFlowThreshold"; }; + readonly meta: { + readonly label: `${string}: High flow threshold`; + readonly min: 0; + readonly unit: "l/h"; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9148,12 +9921,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorMaximumFlow: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Maximum flow detected`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9165,6 +9932,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorMaximumFlow"; }; + readonly meta: { + readonly label: `${string}: Error - Maximum flow detected`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9176,15 +9949,7 @@ export const IrrigationCCValues: Readonly<{ readonly autoCreate: true; }; }; - maximumFlow: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Maximum flow`; - readonly min: 0; - readonly unit: "l/h"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; + maximumFlow: ((valveId: ValveId) => { readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9196,6 +9961,14 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "maximumFlow"; }; + readonly meta: { + readonly label: `${string}: Maximum flow`; + readonly min: 0; + readonly unit: "l/h"; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9208,12 +9981,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorLowCurrent: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Current below low threshold`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9225,6 +9992,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorLowCurrent"; }; + readonly meta: { + readonly label: `${string}: Error - Current below low threshold`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9237,12 +10010,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorHighCurrent: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Current above high threshold`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9254,6 +10021,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorHighCurrent"; }; + readonly meta: { + readonly label: `${string}: Error - Current above high threshold`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9266,12 +10039,6 @@ export const IrrigationCCValues: Readonly<{ }; }; errorShortCircuit: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Error - Short circuit detected`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9283,6 +10050,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "errorShortCircuit"; }; + readonly meta: { + readonly label: `${string}: Error - Short circuit detected`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9295,15 +10068,6 @@ export const IrrigationCCValues: Readonly<{ }; }; nominalCurrentLowThreshold: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Nominal current - low threshold`; - readonly min: 0; - readonly max: 2550; - readonly unit: "mA"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9315,6 +10079,15 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "nominalCurrentLowThreshold"; }; + readonly meta: { + readonly label: `${string}: Nominal current - low threshold`; + readonly min: 0; + readonly max: 2550; + readonly unit: "mA"; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9327,15 +10100,6 @@ export const IrrigationCCValues: Readonly<{ }; }; nominalCurrentHighThreshold: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Nominal current - high threshold`; - readonly min: 0; - readonly max: 2550; - readonly unit: "mA"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9347,6 +10111,15 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "nominalCurrentHighThreshold"; }; + readonly meta: { + readonly label: `${string}: Nominal current - high threshold`; + readonly min: 0; + readonly max: 2550; + readonly unit: "mA"; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9359,13 +10132,6 @@ export const IrrigationCCValues: Readonly<{ }; }; nominalCurrent: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Nominal current`; - readonly unit: "mA"; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9377,6 +10143,13 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "nominalCurrent"; }; + readonly meta: { + readonly label: `${string}: Nominal current`; + readonly unit: "mA"; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -9389,12 +10162,6 @@ export const IrrigationCCValues: Readonly<{ }; }; valveConnected: ((valveId: ValveId) => { - readonly meta: { - readonly label: `${string}: Connected`; - readonly writeable: false; - readonly type: "boolean"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Irrigation; property: ValveId; @@ -9406,6 +10173,12 @@ export const IrrigationCCValues: Readonly<{ readonly property: ValveId; readonly propertyKey: "valveConnected"; }; + readonly meta: { + readonly label: `${string}: Connected`; + readonly writeable: false; + readonly type: "boolean"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -10007,11 +10780,11 @@ export const IrrigationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -10032,11 +10805,11 @@ export const IrrigationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -10057,11 +10830,11 @@ export const IrrigationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -10082,11 +10855,11 @@ export const IrrigationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -10096,11 +10869,13 @@ export const IrrigationCCValues: Readonly<{ // // @public (undocumented) export class IrrigationCCValveConfigGet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveConfigGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveConfigGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) valveId: ValveId; } @@ -10108,7 +10883,7 @@ export class IrrigationCCValveConfigGet extends IrrigationCC { // Warning: (ae-missing-release-tag) "IrrigationCCValveConfigGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveConfigGetOptions extends CCCommandOptions { +export interface IrrigationCCValveConfigGetOptions { // (undocumented) valveId: ValveId; } @@ -10117,7 +10892,9 @@ export interface IrrigationCCValveConfigGetOptions extends CCCommandOptions { // // @public (undocumented) export class IrrigationCCValveConfigReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCValveConfigReport; // (undocumented) highFlowThreshold: number; // (undocumented) @@ -10129,9 +10906,31 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { // (undocumented) nominalCurrentLowThreshold: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; + // (undocumented) + useMoistureSensor: boolean; + // (undocumented) + useRainSensor: boolean; + // (undocumented) + valveId: ValveId; +} + +// Warning: (ae-missing-release-tag) "IrrigationCCValveConfigReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCValveConfigReportOptions { + // (undocumented) + highFlowThreshold: number; + // (undocumented) + lowFlowThreshold: number; + // (undocumented) + maximumFlow: number; + // (undocumented) + nominalCurrentHighThreshold: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + nominalCurrentLowThreshold: number; // (undocumented) useMoistureSensor: boolean; // (undocumented) @@ -10144,7 +10943,9 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { // // @public (undocumented) export class IrrigationCCValveConfigSet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (IrrigationCCValveConfigSetOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveConfigSet; // (undocumented) highFlowThreshold: number; // (undocumented) @@ -10156,9 +10957,9 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { // (undocumented) nominalCurrentLowThreshold: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) useMoistureSensor: boolean; // (undocumented) @@ -10185,11 +10986,13 @@ export type IrrigationCCValveConfigSetOptions = { // // @public (undocumented) export class IrrigationCCValveInfoGet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveInfoGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveInfoGet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) valveId: ValveId; } @@ -10197,7 +11000,7 @@ export class IrrigationCCValveInfoGet extends IrrigationCC { // Warning: (ae-missing-release-tag) "IrrigationCCValveInfoGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveInfoGetOptions extends CCCommandOptions { +export interface IrrigationCCValveInfoGetOptions { // (undocumented) valveId: ValveId; } @@ -10206,7 +11009,7 @@ export interface IrrigationCCValveInfoGetOptions extends CCCommandOptions { // // @public (undocumented) export class IrrigationCCValveInfoReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly connected: boolean; // (undocumented) @@ -10222,26 +11025,54 @@ export class IrrigationCCValveInfoReport extends IrrigationCC { // (undocumented) readonly errorShortCircuit: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCValveInfoReport; + // (undocumented) readonly nominalCurrent: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly valveId: ValveId; } +// Warning: (ae-missing-release-tag) "IrrigationCCValveInfoReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCValveInfoReportOptions { + // (undocumented) + connected: boolean; + // (undocumented) + errorHighCurrent: boolean; + // (undocumented) + errorHighFlow?: boolean; + // (undocumented) + errorLowCurrent: boolean; + // (undocumented) + errorLowFlow?: boolean; + // (undocumented) + errorMaximumFlow?: boolean; + // (undocumented) + errorShortCircuit: boolean; + // (undocumented) + nominalCurrent: number; + // (undocumented) + valveId: ValveId; +} + // Warning: (ae-missing-release-tag) "IrrigationCCValveRun" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class IrrigationCCValveRun extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveRunOptions); + constructor(options: WithAddress); // (undocumented) duration: number; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveRun; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) valveId: ValveId; } @@ -10249,7 +11080,7 @@ export class IrrigationCCValveRun extends IrrigationCC { // Warning: (ae-missing-release-tag) "IrrigationCCValveRunOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveRunOptions extends CCCommandOptions { +export interface IrrigationCCValveRunOptions { // (undocumented) duration: number; // (undocumented) @@ -10260,19 +11091,21 @@ export interface IrrigationCCValveRunOptions extends CCCommandOptions { // // @public (undocumented) export class IrrigationCCValveTableGet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveTableGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveTableGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) tableId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IrrigationCCValveTableGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveTableGetOptions extends CCCommandOptions { +export interface IrrigationCCValveTableGetOptions { // (undocumented) tableId: number; } @@ -10281,32 +11114,46 @@ export interface IrrigationCCValveTableGetOptions extends CCCommandOptions { // // @public (undocumented) export class IrrigationCCValveTableReport extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly entries: ValveTableEntry[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): IrrigationCCValveTableReport; + // (undocumented) readonly tableId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "IrrigationCCValveTableReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IrrigationCCValveTableReportOptions { + // (undocumented) + entries: ValveTableEntry[]; + // (undocumented) + tableId: number; } // Warning: (ae-missing-release-tag) "IrrigationCCValveTableRun" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class IrrigationCCValveTableRun extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveTableRunOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveTableRun; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) tableIDs: number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IrrigationCCValveTableRunOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveTableRunOptions extends CCCommandOptions { +export interface IrrigationCCValveTableRunOptions { // (undocumented) tableIDs: number[]; } @@ -10315,21 +11162,23 @@ export interface IrrigationCCValveTableRunOptions extends CCCommandOptions { // // @public (undocumented) export class IrrigationCCValveTableSet extends IrrigationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | IrrigationCCValveTableSetOptions); + constructor(options: WithAddress); // (undocumented) entries: ValveTableEntry[]; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): IrrigationCCValveTableSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) tableId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "IrrigationCCValveTableSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IrrigationCCValveTableSetOptions extends CCCommandOptions { +export interface IrrigationCCValveTableSetOptions { // (undocumented) entries: ValveTableEntry[]; // (undocumented) @@ -10388,11 +11237,6 @@ export enum IrrigationSensorPolarity { Low = 0 } -// Warning: (ae-missing-release-tag) "isCommandClassContainer" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function isCommandClassContainer(msg: T | undefined): msg is T & ICommandClassContainer; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "isEncapsulatingCommandClass" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -10466,9 +11310,9 @@ export class LanguageCC extends CommandClass { // (undocumented) ccCommand: LanguageCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "LanguageCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -10481,36 +11325,50 @@ export class LanguageCCGet extends LanguageCC { // // @public (undocumented) export class LanguageCCReport extends LanguageCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly country: MaybeNotKnown; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): LanguageCCReport; + // (undocumented) readonly language: string; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "LanguageCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface LanguageCCReportOptions { + // (undocumented) + country: MaybeNotKnown; + // (undocumented) + language: string; } // Warning: (ae-missing-release-tag) "LanguageCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class LanguageCCSet extends LanguageCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | LanguageCCSetOptions); + constructor(options: WithAddress); // (undocumented) get country(): MaybeNotKnown; set country(value: MaybeNotKnown); // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): LanguageCCSet; + // (undocumented) get language(): string; set language(value: string); // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "LanguageCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface LanguageCCSetOptions extends CCCommandOptions { +export interface LanguageCCSetOptions { // (undocumented) country?: string; // (undocumented) @@ -10616,9 +11474,9 @@ export class LockCC extends CommandClass { // (undocumented) ccCommand: LockCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "LockCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -10631,30 +11489,42 @@ export class LockCCGet extends LockCC { // // @public (undocumented) export class LockCCReport extends LockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): LockCCReport; // (undocumented) readonly locked: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "LockCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface LockCCReportOptions { + // (undocumented) + locked: boolean; } // Warning: (ae-missing-release-tag) "LockCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class LockCCSet extends LockCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | LockCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): LockCCSet; // (undocumented) locked: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "LockCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface LockCCSetOptions extends CCCommandOptions { +export interface LockCCSetOptions { // (undocumented) locked: boolean; } @@ -10720,25 +11590,27 @@ export const manufacturerProprietaryAPI: (manufacturerId: // // @public (undocumented) export class ManufacturerProprietaryCC extends CommandClass { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ManufacturerProprietaryCCOptions); + constructor(options: WithAddress); // (undocumented) ccCommand: undefined; // (undocumented) createSpecificInstance(): ManufacturerProprietaryCC | undefined; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + static from(raw: CCRaw, ctx: CCParsingContext_2): ManufacturerProprietaryCC; + // (undocumented) + interview(ctx: InterviewContext): Promise; // (undocumented) manufacturerId?: number; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; } // Warning: (ae-missing-release-tag) "ManufacturerProprietaryCCOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ManufacturerProprietaryCCOptions extends CCCommandOptions { +export interface ManufacturerProprietaryCCOptions { // (undocumented) manufacturerId?: number; // (undocumented) @@ -10754,26 +11626,28 @@ export class ManufacturerSpecificCC extends CommandClass { // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "ManufacturerSpecificCCDeviceSpecificGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ManufacturerSpecificCCDeviceSpecificGet extends ManufacturerSpecificCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ManufacturerSpecificCCDeviceSpecificGetOptions); + constructor(options: WithAddress); // (undocumented) deviceIdType: DeviceIdType; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ManufacturerSpecificCCDeviceSpecificGet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ManufacturerSpecificCCDeviceSpecificGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ManufacturerSpecificCCDeviceSpecificGetOptions extends CCCommandOptions { +export interface ManufacturerSpecificCCDeviceSpecificGetOptions { // (undocumented) deviceIdType: DeviceIdType; } @@ -10782,15 +11656,27 @@ export interface ManufacturerSpecificCCDeviceSpecificGetOptions extends CCComman // // @public (undocumented) export class ManufacturerSpecificCCDeviceSpecificReport extends ManufacturerSpecificCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly deviceId: string; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + static from(raw: CCRaw, ctx: CCParsingContext_2): ManufacturerSpecificCCDeviceSpecificReport; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly type: DeviceIdType; } +// Warning: (ae-missing-release-tag) "ManufacturerSpecificCCDeviceSpecificReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ManufacturerSpecificCCDeviceSpecificReportOptions { + // (undocumented) + deviceId: string; + // (undocumented) + type: DeviceIdType; +} + // Warning: (ae-missing-release-tag) "ManufacturerSpecificCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -10801,7 +11687,9 @@ export class ManufacturerSpecificCCGet extends ManufacturerSpecificCC { // // @public (undocumented) export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { - constructor(host: ZWaveHost_2, options: (ManufacturerSpecificCCReportOptions & CCCommandOptions) | CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ManufacturerSpecificCCReport; // (undocumented) readonly manufacturerId: number; // (undocumented) @@ -10809,9 +11697,9 @@ export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { // (undocumented) readonly productType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ManufacturerSpecificCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -10831,12 +11719,6 @@ export interface ManufacturerSpecificCCReportOptions { // @public (undocumented) export const ManufacturerSpecificCCValues: Readonly<{ deviceId: ((type: DeviceIdType) => { - readonly meta: { - readonly label: string; - readonly writeable: false; - readonly type: "string"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Manufacturer Specific"]; property: "deviceId"; @@ -10848,14 +11730,20 @@ export const ManufacturerSpecificCCValues: Readonly<{ readonly property: "deviceId"; readonly propertyKey: string; }; + readonly meta: { + readonly label: string; + readonly writeable: false; + readonly type: "string"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -10879,11 +11767,11 @@ export const ManufacturerSpecificCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -10907,11 +11795,11 @@ export const ManufacturerSpecificCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -10935,11 +11823,11 @@ export const ManufacturerSpecificCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -10959,47 +11847,41 @@ export enum ManufacturerSpecificCommand { Report = 5 } -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@publicAPI" is not defined in this configuration -// Warning: (ae-missing-release-tag) "messageIsPing" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function messageIsPing(msg: T): msg is T & { - command: NoOperationCC; -}; - // Warning: (ae-missing-release-tag) "MeterCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MeterCC extends CommandClass { // (undocumented) ccCommand: MeterCommand; - static getMeterTypeCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint): MaybeNotKnown; - static getSupportedRateTypesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint): MaybeNotKnown; - static getSupportedScalesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint): MaybeNotKnown; + static getMeterTypeCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getSupportedRateTypesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getSupportedScalesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - shouldRefreshValues(this: SinglecastCC_2, applHost: ZWaveApplicationHost_2): boolean; - static supportsResetCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint): MaybeNotKnown; + shouldRefreshValues(this: SinglecastCC_2, ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): boolean; + static supportsResetCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "MeterCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MeterCCGet extends MeterCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MeterCCGetOptions & CCCommandOptions)); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MeterCCGet; // (undocumented) rateType: RateType | undefined; // (undocumented) scale: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MeterCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -11016,11 +11898,13 @@ export interface MeterCCGetOptions { // // @public (undocumented) export class MeterCCReport extends MeterCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MeterCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress_2); // (undocumented) deltaTime: MaybeUnknown_2; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): MeterCCReport; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) previousValue: MaybeNotKnown; // (undocumented) @@ -11028,9 +11912,9 @@ export class MeterCCReport extends MeterCC { // (undocumented) scale: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: number; // (undocumented) @@ -11059,17 +11943,19 @@ export interface MeterCCReportOptions { // // @public (undocumented) export class MeterCCReset extends MeterCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MeterCCResetOptions & CCCommandOptions)); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MeterCCReset; // (undocumented) rateType: RateType | undefined; // (undocumented) scale: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetValue: number | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: number | undefined; } @@ -11094,11 +11980,13 @@ export class MeterCCSupportedGet extends MeterCC { // // @public (undocumented) export class MeterCCSupportedReport extends MeterCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MeterCCSupportedReportOptions & CCCommandOptions)); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MeterCCSupportedReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedRateTypes: readonly RateType[]; // (undocumented) @@ -11106,7 +11994,7 @@ export class MeterCCSupportedReport extends MeterCC { // (undocumented) readonly supportsReset: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly type: number; } @@ -11126,20 +12014,10 @@ export interface MeterCCSupportedReportOptions { } // Warning: (ae-missing-release-tag) "MeterCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const MeterCCValues: Readonly<{ - value: ((meterType: number, rateType: RateType, scale: number) => { - readonly meta: { - readonly ccSpecific: { - readonly meterType: number; - readonly rateType: RateType; - readonly scale: number; - }; - readonly writeable: false; - readonly type: "number"; - readonly readable: true; - }; +// +// @public (undocumented) +export const MeterCCValues: Readonly<{ + value: ((meterType: number, rateType: RateType, scale: number) => { readonly id: { commandClass: CommandClasses.Meter; property: "value"; @@ -11151,6 +12029,16 @@ export const MeterCCValues: Readonly<{ readonly property: "value"; readonly propertyKey: number; }; + readonly meta: { + readonly ccSpecific: { + readonly meterType: number; + readonly rateType: RateType; + readonly scale: number; + }; + readonly writeable: false; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -11163,6 +12051,17 @@ export const MeterCCValues: Readonly<{ }; }; resetSingle: ((meterType: number, rateType: RateType, scale: number) => { + readonly id: { + commandClass: CommandClasses.Meter; + property: "reset"; + propertyKey: number; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: CommandClasses.Meter; + readonly endpoint: number; + readonly property: "reset"; + readonly propertyKey: number; + }; readonly meta: { readonly label: `Reset (${string})` | `Reset (Consumption, ${string})` | `Reset (Production, ${string})`; readonly states: { @@ -11177,17 +12076,6 @@ export const MeterCCValues: Readonly<{ readonly type: "boolean"; readonly writeable: true; }; - readonly id: { - commandClass: CommandClasses.Meter; - property: "reset"; - propertyKey: number; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: CommandClasses.Meter; - readonly endpoint: number; - readonly property: "reset"; - readonly propertyKey: number; - }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -11245,11 +12133,11 @@ export const MeterCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11270,11 +12158,11 @@ export const MeterCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11295,11 +12183,11 @@ export const MeterCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11320,11 +12208,11 @@ export const MeterCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11440,32 +12328,34 @@ export class MultiChannelAssociationCC extends CommandClass { ccCommand: MultiChannelAssociationCommand; // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; - static getAllDestinationsCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): ReadonlyMap; - static getGroupCountCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): number; - static getMaxNodesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, groupId: number): number; + static getAllDestinationsCached(ctx: GetValueDB_2, endpoint: EndpointId_2): ReadonlyMap; + static getGroupCountCached(ctx: GetValueDB_2, endpoint: EndpointId_2): number; + static getMaxNodesCached(ctx: GetValueDB_2, endpoint: EndpointId_2, groupId: number): number; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultiChannelAssociationCCGet extends MultiChannelAssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelAssociationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelAssociationCCGet; // (undocumented) groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelAssociationCCGetOptions extends CCCommandOptions { +export interface MultiChannelAssociationCCGetOptions { // (undocumented) groupId: number; } @@ -11474,17 +12364,19 @@ export interface MultiChannelAssociationCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MultiChannelAssociationCCRemoveOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) endpoints?: EndpointAddress[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelAssociationCCRemove; + // (undocumented) groupId?: number; // (undocumented) nodeIds?: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCRemoveOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -11500,27 +12392,29 @@ export interface MultiChannelAssociationCCRemoveOptions { // // @public (undocumented) export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MultiChannelAssociationCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) endpoints: EndpointAddress[]; // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelAssociationCCReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) readonly groupId: number; // (undocumented) maxNodes: number; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: MultiChannelAssociationCCReport[]): void; + mergePartialCCs(partials: MultiChannelAssociationCCReport[], _ctx: CCParsingContext_2): void; // (undocumented) nodeIds: number[]; // (undocumented) reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -11543,17 +12437,19 @@ export interface MultiChannelAssociationCCReportOptions { // // @public (undocumented) export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (MultiChannelAssociationCCSetOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) endpoints: EndpointAddress[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelAssociationCCSet; + // (undocumented) groupId: number; // (undocumented) nodeIds: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -11580,19 +12476,21 @@ export class MultiChannelAssociationCCSupportedGroupingsGet extends MultiChannel // // @public (undocumented) export class MultiChannelAssociationCCSupportedGroupingsReport extends MultiChannelAssociationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelAssociationCCSupportedGroupingsReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelAssociationCCSupportedGroupingsReport; // (undocumented) readonly groupCount: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelAssociationCCSupportedGroupingsReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelAssociationCCSupportedGroupingsReportOptions extends CCCommandOptions { +export interface MultiChannelAssociationCCSupportedGroupingsReportOptions { // (undocumented) groupCount: number; } @@ -11602,11 +12500,6 @@ export interface MultiChannelAssociationCCSupportedGroupingsReportOptions extend // @public (undocumented) export const MultiChannelAssociationCCValues: Readonly<{ endpoints: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multi Channel Association"]; property: "endpoints"; @@ -11618,23 +12511,23 @@ export const MultiChannelAssociationCCValues: Readonly<{ readonly property: "endpoints"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; nodeIds: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multi Channel Association"]; property: "nodeIds"; @@ -11646,23 +12539,23 @@ export const MultiChannelAssociationCCValues: Readonly<{ readonly property: "nodeIds"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; maxNodes: ((groupId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multi Channel Association"]; property: "maxNodes"; @@ -11674,14 +12567,19 @@ export const MultiChannelAssociationCCValues: Readonly<{ readonly property: "maxNodes"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11702,11 +12600,11 @@ export const MultiChannelAssociationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -11736,9 +12634,10 @@ export enum MultiChannelAssociationCommand { export class MultiChannelCC extends CommandClass { // (undocumented) ccCommand: MultiChannelCommand; - static encapsulate(host: ZWaveHost_2, cc: CommandClass): MultiChannelCCCommandEncapsulation | MultiChannelCCV1CommandEncapsulation; + static encapsulate(cc: CommandClass): MultiChannelCCCommandEncapsulation; + static encapsulateV1(cc: CommandClass): MultiChannelCCV1CommandEncapsulation; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; static requiresEncapsulation(cc: CommandClass): boolean; // (undocumented) skipEndpointInterview(): boolean; @@ -11748,19 +12647,21 @@ export class MultiChannelCC extends CommandClass { // // @public (undocumented) export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCAggregatedMembersGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): MultiChannelCCAggregatedMembersGet; // (undocumented) requestedEndpoint: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCAggregatedMembersGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCAggregatedMembersGetOptions extends CCCommandOptions { +export interface MultiChannelCCAggregatedMembersGetOptions { // (undocumented) requestedEndpoint: number; } @@ -11769,32 +12670,46 @@ export interface MultiChannelCCAggregatedMembersGetOptions extends CCCommandOpti // // @public (undocumented) export class MultiChannelCCAggregatedMembersReport extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly aggregatedEndpointIndex: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCAggregatedMembersReport; + // (undocumented) readonly members: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "MultiChannelCCAggregatedMembersReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MultiChannelCCAggregatedMembersReportOptions { + // (undocumented) + aggregatedEndpointIndex: number; + // (undocumented) + members: number[]; } // Warning: (ae-missing-release-tag) "MultiChannelCCCapabilityGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultiChannelCCCapabilityGet extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCCapabilityGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCCapabilityGet; // (undocumented) requestedEndpoint: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCCapabilityGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCCapabilityGetOptions extends CCCommandOptions { +export interface MultiChannelCCCapabilityGetOptions { // (undocumented) requestedEndpoint: number; } @@ -11803,21 +12718,23 @@ export interface MultiChannelCCCapabilityGetOptions extends CCCommandOptions { // // @public (undocumented) export class MultiChannelCCCapabilityReport extends MultiChannelCC implements ApplicationNodeInformation { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCCapabilityReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCCapabilityReport; // (undocumented) readonly genericDeviceClass: number; // (undocumented) readonly isDynamic: boolean; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly specificDeviceClass: number; // (undocumented) readonly supportedCCs: CommandClasses[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly wasRemoved: boolean; } @@ -11825,7 +12742,7 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC implements Ap // Warning: (ae-missing-release-tag) "MultiChannelCCCapabilityReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCCapabilityReportOptions extends CCCommandOptions { +export interface MultiChannelCCCapabilityReportOptions { // (undocumented) endpointIndex: number; // (undocumented) @@ -11844,7 +12761,7 @@ export interface MultiChannelCCCapabilityReportOptions extends CCCommandOptions // // @public (undocumented) export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCCommandEncapsulationOptions); + constructor(options: WithAddress); // (undocumented) protected computeEncapsulationOverhead(): number; // Warning: (ae-forgotten-export) The symbol "MultiChannelCCDestination" needs to be exported by the entry point index.d.ts @@ -11852,15 +12769,17 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { // (undocumented) encapsulated: CommandClass; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCCommandEncapsulation; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCCommandEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCCommandEncapsulationOptions extends CCCommandOptions { +export interface MultiChannelCCCommandEncapsulationOptions { // (undocumented) destination: MultiChannelCCDestination; // (undocumented) @@ -11871,21 +12790,23 @@ export interface MultiChannelCCCommandEncapsulationOptions extends CCCommandOpti // // @public (undocumented) export class MultiChannelCCEndPointFind extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCEndPointFindOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCEndPointFind; // (undocumented) genericClass: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) specificClass: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCEndPointFindOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCEndPointFindOptions extends CCCommandOptions { +export interface MultiChannelCCEndPointFindOptions { // (undocumented) genericClass: number; // (undocumented) @@ -11896,31 +12817,33 @@ export interface MultiChannelCCEndPointFindOptions extends CCCommandOptions { // // @public (undocumented) export class MultiChannelCCEndPointFindReport extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCEndPointFindReportOptions); + constructor(options: WithAddress); // (undocumented) expectMoreMessages(): boolean; // (undocumented) foundEndpoints: number[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCEndPointFindReport; + // (undocumented) genericClass: number; // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: MultiChannelCCEndPointFindReport[]): void; + mergePartialCCs(partials: MultiChannelCCEndPointFindReport[], _ctx: CCParsingContext_2): void; // (undocumented) reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) specificClass: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCEndPointFindReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCEndPointFindReportOptions extends CCCommandOptions { +export interface MultiChannelCCEndPointFindReportOptions { // (undocumented) foundEndpoints: number[]; // (undocumented) @@ -11941,25 +12864,27 @@ export class MultiChannelCCEndPointGet extends MultiChannelCC { // // @public (undocumented) export class MultiChannelCCEndPointReport extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCEndPointReportOptions); + constructor(options: WithAddress); // (undocumented) aggregatedCount: MaybeNotKnown; // (undocumented) countIsDynamic: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCEndPointReport; + // (undocumented) identicalCapabilities: boolean; // (undocumented) individualCount: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCEndPointReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCEndPointReportOptions extends CCCommandOptions { +export interface MultiChannelCCEndPointReportOptions { // (undocumented) aggregatedCount?: number; // (undocumented) @@ -11974,21 +12899,23 @@ export interface MultiChannelCCEndPointReportOptions extends CCCommandOptions { // // @public (undocumented) export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCV1CommandEncapsulationOptions); + constructor(options: WithAddress); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) encapsulated: CommandClass; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCV1CommandEncapsulation; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCV1CommandEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCV1CommandEncapsulationOptions extends CCCommandOptions { +export interface MultiChannelCCV1CommandEncapsulationOptions { // (undocumented) encapsulated: CommandClass; } @@ -11997,19 +12924,21 @@ export interface MultiChannelCCV1CommandEncapsulationOptions extends CCCommandOp // // @public (undocumented) export class MultiChannelCCV1Get extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiChannelCCV1GetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): MultiChannelCCV1Get; // (undocumented) requestedCC: CommandClasses; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiChannelCCV1GetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiChannelCCV1GetOptions extends CCCommandOptions { +export interface MultiChannelCCV1GetOptions { // (undocumented) requestedCC: CommandClasses; } @@ -12018,13 +12947,25 @@ export interface MultiChannelCCV1GetOptions extends CCCommandOptions { // // @public (undocumented) export class MultiChannelCCV1Report extends MultiChannelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly endpointCount: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiChannelCCV1Report; + // (undocumented) readonly requestedCC: CommandClasses; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "MultiChannelCCV1ReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MultiChannelCCV1ReportOptions { + // (undocumented) + endpointCount: number; + // (undocumented) + requestedCC: CommandClasses; } // Warning: (ae-missing-release-tag) "MultiChannelCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -12032,11 +12973,6 @@ export class MultiChannelCCV1Report extends MultiChannelCC { // @public (undocumented) export const MultiChannelCCValues: Readonly<{ aggregatedEndpointMembers: ((endpointIndex: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multi Channel"]; property: "members"; @@ -12048,14 +12984,19 @@ export const MultiChannelCCValues: Readonly<{ readonly property: "members"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12076,11 +13017,11 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12101,11 +13042,11 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12126,10 +13067,10 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12151,10 +13092,10 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12176,10 +13117,10 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12201,10 +13142,10 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12226,10 +13167,10 @@ export const MultiChannelCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12273,7 +13214,7 @@ export class MultiCommandCC extends CommandClass { // (undocumented) ccCommand: MultiCommandCommand; // (undocumented) - static encapsulate(host: ZWaveHost_2, CCs: CommandClass[]): MultiCommandCCCommandEncapsulation; + static encapsulate(CCs: CommandClass[]): MultiCommandCCCommandEncapsulation; static requiresEncapsulation(cc: CommandClass): boolean; } @@ -12281,19 +13222,21 @@ export class MultiCommandCC extends CommandClass { // // @public (undocumented) export class MultiCommandCCCommandEncapsulation extends MultiCommandCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultiCommandCCCommandEncapsulationOptions); + constructor(options: WithAddress); // (undocumented) encapsulated: CommandClass[]; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): MultiCommandCCCommandEncapsulation; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultiCommandCCCommandEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultiCommandCCCommandEncapsulationOptions extends CCCommandOptions { +export interface MultiCommandCCCommandEncapsulationOptions { // (undocumented) encapsulated: CommandClass[]; } @@ -12310,80 +13253,72 @@ export enum MultiCommandCommand { // // @public (undocumented) export interface MultiEncapsulatingCommandClass { - // (undocumented) - constructor: MultiEncapsulatingCommandClassStatic; // (undocumented) encapsulated: EncapsulatedCommandClass[]; } -// Warning: (ae-missing-release-tag) "MultiEncapsulatingCommandClassStatic" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface MultiEncapsulatingCommandClassStatic { - // (undocumented) - new (applHost: ZWaveApplicationHost, options: CommandClassOptions): MultiEncapsulatingCommandClass; - // (undocumented) - encapsulate(applHost: ZWaveApplicationHost, CCs: CommandClass[]): MultiEncapsulatingCommandClass; - // (undocumented) - requiresEncapsulation(cc: CommandClass): boolean; -} - // Warning: (ae-missing-release-tag) "MultilevelSensorCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultilevelSensorCC extends CommandClass { // (undocumented) ccCommand: MultilevelSensorCommand; - static getSupportedScalesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, sensorType: number): MaybeNotKnown; - static getSupportedSensorTypesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; + static getSupportedScalesCached(ctx: GetValueDB_2, endpoint: EndpointId_2, sensorType: number): MaybeNotKnown; + static getSupportedSensorTypesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - shouldRefreshValues(this: SinglecastCC_2, applHost: ZWaveApplicationHost_2): boolean; + shouldRefreshValues(this: SinglecastCC_2, ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): boolean; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "MultilevelSensorCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultilevelSensorCCGet extends MultilevelSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSensorCCGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSensorCCGet; // (undocumented) scale: number | undefined; // (undocumented) sensorType: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } -// Warning: (ae-forgotten-export) The symbol "MultilevelSensorCCGetSpecificOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "MultilevelSensorCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type MultilevelSensorCCGetOptions = CCCommandOptions | (CCCommandOptions & MultilevelSensorCCGetSpecificOptions); +export type MultilevelSensorCCGetOptions = AllOrNone<{ + sensorType: number; + scale: number; +}>; // Warning: (ae-missing-release-tag) "MultilevelSensorCCGetSupportedScale" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSensorCCGetSupportedScaleOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSensorCCGetSupportedScale; // (undocumented) sensorType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSensorCCGetSupportedScaleOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSensorCCGetSupportedScaleOptions extends CCCommandOptions { +export interface MultilevelSensorCCGetSupportedScaleOptions { // (undocumented) sensorType: number; } @@ -12398,15 +13333,17 @@ export class MultilevelSensorCCGetSupportedSensor extends MultilevelSensorCC { // // @public (undocumented) export class MultilevelSensorCCReport extends MultilevelSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSensorCCReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSensorCCReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) scale: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: number; // (undocumented) @@ -12416,7 +13353,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { // Warning: (ae-missing-release-tag) "MultilevelSensorCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSensorCCReportOptions extends CCCommandOptions { +export interface MultilevelSensorCCReportOptions { // (undocumented) scale: number | Scale; // (undocumented) @@ -12429,21 +13366,23 @@ export interface MultilevelSensorCCReportOptions extends CCCommandOptions { // // @public (undocumented) export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSensorCCSupportedScaleReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSensorCCSupportedScaleReport; // (undocumented) readonly sensorType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedScales: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSensorCCSupportedScaleReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSensorCCSupportedScaleReportOptions extends CCCommandOptions { +export interface MultilevelSensorCCSupportedScaleReportOptions { // (undocumented) sensorType: number; // (undocumented) @@ -12454,19 +13393,21 @@ export interface MultilevelSensorCCSupportedScaleReportOptions extends CCCommand // // @public (undocumented) export class MultilevelSensorCCSupportedSensorReport extends MultilevelSensorCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSensorCCSupportedSensorReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSensorCCSupportedSensorReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportedSensorTypes: readonly number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSensorCCSupportedSensorReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSensorCCSupportedSensorReportOptions extends CCCommandOptions { +export interface MultilevelSensorCCSupportedSensorReportOptions { // (undocumented) supportedSensorTypes: readonly number[]; } @@ -12476,12 +13417,6 @@ export interface MultilevelSensorCCSupportedSensorReportOptions extends CCComman // @public (undocumented) export const MultilevelSensorCCValues: Readonly<{ value: ((sensorTypeName: string) => { - readonly meta: { - readonly label: string; - readonly writeable: false; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multilevel Sensor"]; property: string; @@ -12491,6 +13426,12 @@ export const MultilevelSensorCCValues: Readonly<{ readonly endpoint: number; readonly property: string; }; + readonly meta: { + readonly label: string; + readonly writeable: false; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -12503,11 +13444,6 @@ export const MultilevelSensorCCValues: Readonly<{ }; }; supportedScales: ((sensorType: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multilevel Sensor"]; property: "supportedScales"; @@ -12519,14 +13455,19 @@ export const MultilevelSensorCCValues: Readonly<{ readonly property: "supportedScales"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12547,11 +13488,11 @@ export const MultilevelSensorCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12602,13 +13543,13 @@ export class MultilevelSwitchCC extends CommandClass { // (undocumented) ccCommand: MultilevelSwitchCommand; // (undocumented) - protected createMetadataForLevelChangeActions(applHost: ZWaveApplicationHost_2, switchType?: SwitchType): void; + protected createMetadataForLevelChangeActions(ctx: GetValueDB_2, switchType?: SwitchType): void; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - setMappedBasicValue(applHost: ZWaveApplicationHost_2, value: number): boolean; + setMappedBasicValue(ctx: GetValueDB_2, value: number): boolean; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -12621,50 +13562,54 @@ export class MultilevelSwitchCCGet extends MultilevelSwitchCC { // // @public (undocumented) export class MultilevelSwitchCCReport extends MultilevelSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSwitchCCReportOptions); + constructor(options: WithAddress); // (undocumented) currentValue: MaybeUnknown | undefined; // (undocumented) duration: Duration | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSwitchCCReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetValue: MaybeUnknown | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSwitchCCReportOptions extends CCCommandOptions { +export interface MultilevelSwitchCCReportOptions { // (undocumented) - currentValue: number; + currentValue?: MaybeUnknown; // (undocumented) duration?: Duration | string; // (undocumented) - targetValue: number; + targetValue?: MaybeUnknown; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class MultilevelSwitchCCSet extends MultilevelSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | MultilevelSwitchCCSetOptions); + constructor(options: WithAddress); // (undocumented) duration: Duration | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSwitchCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) targetValue: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MultilevelSwitchCCSetOptions extends CCCommandOptions { +export interface MultilevelSwitchCCSetOptions { // (undocumented) duration?: Duration | string; // (undocumented) @@ -12675,19 +13620,21 @@ export interface MultilevelSwitchCCSetOptions extends CCCommandOptions { // // @public (undocumented) export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & MultilevelSwitchCCStartLevelChangeOptions)); + constructor(options: WithAddress); // (undocumented) direction: keyof typeof LevelChangeDirection; // (undocumented) duration: Duration | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSwitchCCStartLevelChange; + // (undocumented) ignoreStartLevel: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) startLevel: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCStartLevelChangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -12721,13 +13668,25 @@ export class MultilevelSwitchCCSupportedGet extends MultilevelSwitchCC { // // @public (undocumented) export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): MultilevelSwitchCCSupportedReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly switchType: SwitchType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "MultilevelSwitchCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MultilevelSwitchCCSupportedReportOptions { + // (undocumented) + switchType: SwitchType; } // Warning: (ae-missing-release-tag) "MultilevelSwitchCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -12735,6 +13694,15 @@ export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { // @public (undocumented) export const MultilevelSwitchCCValues: Readonly<{ levelChangeDown: ((switchType: SwitchType) => { + readonly id: { + commandClass: (typeof CommandClasses)["Multilevel Switch"]; + property: string; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses)["Multilevel Switch"]; + readonly endpoint: number; + readonly property: string; + }; readonly meta: { readonly label: `Perform a level change (${string})`; readonly valueChangeOptions: readonly ["transitionDuration"]; @@ -12749,15 +13717,6 @@ export const MultilevelSwitchCCValues: Readonly<{ readonly type: "boolean"; readonly writeable: true; }; - readonly id: { - commandClass: (typeof CommandClasses)["Multilevel Switch"]; - property: string; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses)["Multilevel Switch"]; - readonly endpoint: number; - readonly property: string; - }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -12770,20 +13729,6 @@ export const MultilevelSwitchCCValues: Readonly<{ }; }; levelChangeUp: ((switchType: SwitchType) => { - readonly meta: { - readonly label: `Perform a level change (${string})`; - readonly valueChangeOptions: readonly ["transitionDuration"]; - readonly states: { - readonly true: "Start"; - readonly false: "Stop"; - }; - readonly ccSpecific: { - readonly switchType: SwitchType; - }; - readonly readable: false; - readonly type: "boolean"; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Multilevel Switch"]; property: string; @@ -12793,6 +13738,20 @@ export const MultilevelSwitchCCValues: Readonly<{ readonly endpoint: number; readonly property: string; }; + readonly meta: { + readonly label: `Perform a level change (${string})`; + readonly valueChangeOptions: readonly ["transitionDuration"]; + readonly states: { + readonly true: "Start"; + readonly false: "Stop"; + }; + readonly ccSpecific: { + readonly switchType: SwitchType; + }; + readonly readable: false; + readonly type: "boolean"; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -12821,10 +13780,10 @@ export const MultilevelSwitchCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -12846,11 +13805,11 @@ export const MultilevelSwitchCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -12874,12 +13833,12 @@ export const MultilevelSwitchCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly secret: false; readonly internal: false; - readonly minVersion: 1; readonly supportsEndpoints: true; + readonly secret: false; + readonly minVersion: 1; readonly stateful: false; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (applHost: GetValueDB_2 & GetDeviceConfig_2, endpoint: EndpointId_2) => boolean; }; }; restorePrevious: { @@ -13052,9 +14011,9 @@ export class NodeNamingAndLocationCC extends CommandClass { // (undocumented) ccCommand: NodeNamingAndLocationCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) skipEndpointInterview(): boolean; } @@ -13069,30 +14028,42 @@ export class NodeNamingAndLocationCCLocationGet extends NodeNamingAndLocationCC // // @public (undocumented) export class NodeNamingAndLocationCCLocationReport extends NodeNamingAndLocationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NodeNamingAndLocationCCLocationReport; // (undocumented) readonly location: string; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCLocationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface NodeNamingAndLocationCCLocationReportOptions { + // (undocumented) + location: string; } // Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCLocationSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class NodeNamingAndLocationCCLocationSet extends NodeNamingAndLocationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NodeNamingAndLocationCCLocationSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): NodeNamingAndLocationCCLocationSet; // (undocumented) location: string; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCLocationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NodeNamingAndLocationCCLocationSetOptions extends CCCommandOptions { +export interface NodeNamingAndLocationCCLocationSetOptions { // (undocumented) location: string; } @@ -13107,30 +14078,42 @@ export class NodeNamingAndLocationCCNameGet extends NodeNamingAndLocationCC { // // @public (undocumented) export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NodeNamingAndLocationCCNameReport; // (undocumented) readonly name: string; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCNameReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface NodeNamingAndLocationCCNameReportOptions { + // (undocumented) + name: string; } // Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCNameSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NodeNamingAndLocationCCNameSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): NodeNamingAndLocationCCNameSet; // (undocumented) name: string; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NodeNamingAndLocationCCNameSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NodeNamingAndLocationCCNameSetOptions extends CCCommandOptions { +export interface NodeNamingAndLocationCCNameSetOptions { // (undocumented) name: string; } @@ -13157,11 +14140,11 @@ export const NodeNamingAndLocationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -13183,11 +14166,11 @@ export const NodeNamingAndLocationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -13232,32 +14215,34 @@ export class NotificationCC extends CommandClass { ccCommand: NotificationCommand; // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; - static getNotificationMode(applHost: ZWaveApplicationHost_2, node: IZWaveNode): MaybeNotKnown<"push" | "pull">; + static getNotificationMode(ctx: GetValueDB_2, node: NodeId): MaybeNotKnown<"push" | "pull">; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - shouldRefreshValues(this: SinglecastCC_2, applHost: ZWaveApplicationHost_2): boolean; + shouldRefreshValues(this: SinglecastCC_2, ctx: GetValueDB_2 & GetSupportedCCVersion_2 & GetDeviceConfig_2 & GetNode_2>): boolean; } // Warning: (ae-missing-release-tag) "NotificationCCEventSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class NotificationCCEventSupportedGet extends NotificationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NotificationCCEventSupportedGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCEventSupportedGet; // (undocumented) notificationType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NotificationCCEventSupportedGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NotificationCCEventSupportedGetOptions extends CCCommandOptions { +export interface NotificationCCEventSupportedGetOptions { // (undocumented) notificationType: number; } @@ -13266,23 +14251,25 @@ export interface NotificationCCEventSupportedGetOptions extends CCCommandOptions // // @public (undocumented) export class NotificationCCEventSupportedReport extends NotificationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NotificationCCEventSupportedReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCEventSupportedReport; // (undocumented) notificationType: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportedEvents: number[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NotificationCCEventSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NotificationCCEventSupportedReportOptions extends CCCommandOptions { +export interface NotificationCCEventSupportedReportOptions { // (undocumented) notificationType: number; // (undocumented) @@ -13293,28 +14280,34 @@ export interface NotificationCCEventSupportedReportOptions extends CCCommandOpti // // @public (undocumented) export class NotificationCCGet extends NotificationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NotificationCCGetOptions); + constructor(options: WithAddress_2); alarmType: number | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCGet; + // (undocumented) notificationEvent: number | undefined; notificationType: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } -// Warning: (ae-forgotten-export) The symbol "NotificationCCGetSpecificOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "NotificationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type NotificationCCGetOptions = CCCommandOptions & NotificationCCGetSpecificOptions; +export type NotificationCCGetOptions = { + alarmType: number; +} | { + notificationType: number; + notificationEvent?: number; +}; // Warning: (ae-missing-release-tag) "NotificationCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class NotificationCCReport extends NotificationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (NotificationCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress_2); // (undocumented) alarmLevel: number | undefined; // (undocumented) @@ -13322,32 +14315,32 @@ export class NotificationCCReport extends NotificationCC { // (undocumented) eventParameters: Buffer | Duration | Record | number | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCReport; + // (undocumented) notificationEvent: number | undefined; // (undocumented) notificationStatus: boolean | number | undefined; // (undocumented) notificationType: number | undefined; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) sequenceNumber: number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; - // (undocumented) - readonly zensorNetSourceNodeId: number | undefined; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NotificationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export type NotificationCCReportOptions = { - alarmType: number; - alarmLevel: number; -} | { - notificationType: number; - notificationEvent: number; + alarmType?: number; + alarmLevel?: number; + notificationType?: number; + notificationEvent?: number; + notificationStatus?: number; eventParameters?: Buffer; sequenceNumber?: number; }; @@ -13356,21 +14349,23 @@ export type NotificationCCReportOptions = { // // @public (undocumented) export class NotificationCCSet extends NotificationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | NotificationCCSetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCSet; // (undocumented) notificationStatus: boolean; // (undocumented) notificationType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NotificationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NotificationCCSetOptions extends CCCommandOptions { +export interface NotificationCCSetOptions { // (undocumented) notificationStatus: boolean; // (undocumented) @@ -13387,21 +14382,23 @@ export class NotificationCCSupportedGet extends NotificationCC { // // @public (undocumented) export class NotificationCCSupportedReport extends NotificationCC { - constructor(host: ZWaveHost_2, options: NotificationCCSupportedReportOptions | CommandClassDeserializationOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): NotificationCCSupportedReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportedNotificationTypes: number[]; // (undocumented) supportsV1Alarm: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "NotificationCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface NotificationCCSupportedReportOptions extends CCCommandOptions { +export interface NotificationCCSupportedReportOptions { // (undocumented) supportedNotificationTypes: number[]; // (undocumented) @@ -13413,11 +14410,6 @@ export interface NotificationCCSupportedReportOptions extends CCCommandOptions { // @public (undocumented) export const NotificationCCValues: Readonly<{ notificationVariable: ((notificationName: string, variableName: string) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Notification; property: string; @@ -13429,6 +14421,11 @@ export const NotificationCCValues: Readonly<{ readonly property: string; readonly propertyKey: string; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -13441,17 +14438,6 @@ export const NotificationCCValues: Readonly<{ }; }; unknownNotificationVariable: ((notificationType: number, notificationName: string) => { - readonly meta: { - readonly label: `${string}: Unknown value`; - readonly ccSpecific: { - readonly notificationType: number; - }; - readonly writeable: false; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - }; readonly id: { commandClass: CommandClasses.Notification; property: string; @@ -13463,6 +14449,17 @@ export const NotificationCCValues: Readonly<{ readonly property: string; readonly propertyKey: "unknown"; }; + readonly meta: { + readonly label: `${string}: Unknown value`; + readonly ccSpecific: { + readonly notificationType: number; + }; + readonly writeable: false; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -13475,6 +14472,15 @@ export const NotificationCCValues: Readonly<{ }; }; unknownNotificationType: ((notificationType: number) => { + readonly id: { + commandClass: CommandClasses.Notification; + property: string; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: CommandClasses.Notification; + readonly endpoint: number; + readonly property: string; + }; readonly meta: { readonly label: `Unknown notification (${string})`; readonly ccSpecific: { @@ -13486,15 +14492,6 @@ export const NotificationCCValues: Readonly<{ readonly type: "number"; readonly readable: true; }; - readonly id: { - commandClass: CommandClasses.Notification; - property: string; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: CommandClasses.Notification; - readonly endpoint: number; - readonly property: string; - }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -13507,11 +14504,6 @@ export const NotificationCCValues: Readonly<{ }; }; supportedNotificationEvents: ((notificationType: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Notification; property: "supportedNotificationEvents"; @@ -13523,13 +14515,18 @@ export const NotificationCCValues: Readonly<{ readonly property: "supportedNotificationEvents"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -13563,11 +14560,11 @@ export const NotificationCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly supportsEndpoints: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly supportsEndpoints: true; readonly autoCreate: false; }; }; @@ -13600,12 +14597,12 @@ export const NotificationCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly supportsEndpoints: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: (applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2) => boolean; + readonly autoCreate: (ctx: GetValueDB_2, endpoint: EndpointId_2) => boolean; }; }; alarmLevel: { @@ -13681,11 +14678,11 @@ export const NotificationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -13706,10 +14703,10 @@ export const NotificationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -13731,10 +14728,10 @@ export const NotificationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -13756,10 +14753,10 @@ export const NotificationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -13810,15 +14807,25 @@ export function parseWakeUpTime(value: number): WakeUpTime; // @public (undocumented) export type PartialCCValuePredicate = (properties: ValueIDProperties) => boolean; +// Warning: (ae-missing-release-tag) "PersistValuesContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type PersistValuesContext = HostIDs & GetValueDB & GetSupportedCCVersion & GetDeviceConfig & GetNode> & LogNode; + // Warning: (ae-missing-release-tag) "PhysicalCCAPI" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public export class PhysicalCCAPI extends CCAPI { - constructor(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint); + constructor(host: CCAPIHost, endpoint: CCAPIEndpoint); // (undocumented) - protected readonly endpoint: IZWaveEndpoint; + protected readonly endpoint: PhysicalCCAPIEndpoint; } +// Warning: (ae-missing-release-tag) "PhysicalCCAPIEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type PhysicalCCAPIEndpoint = CCAPIEndpoint & EndpointId; + // Warning: (ae-missing-release-tag) "POLL_VALUE" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -13873,15 +14880,17 @@ export class PowerlevelCCGet extends PowerlevelCC { // // @public (undocumented) export class PowerlevelCCReport extends PowerlevelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (PowerlevelCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): PowerlevelCCReport; // (undocumented) readonly powerlevel: Powerlevel; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly timeout?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "PowerlevelCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -13899,27 +14908,29 @@ export type PowerlevelCCReportOptions = { // // @public (undocumented) export class PowerlevelCCSet extends PowerlevelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | PowerlevelCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): PowerlevelCCSet; // (undocumented) powerlevel: Powerlevel; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) timeout?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "PowerlevelCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type PowerlevelCCSetOptions = CCCommandOptions & ({ +export type PowerlevelCCSetOptions = { powerlevel: Powerlevel; timeout: number; } | { powerlevel: (typeof Powerlevel)["Normal Power"]; timeout?: undefined; -}); +}; // Warning: (ae-missing-release-tag) "PowerlevelCCTestNodeGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -13931,17 +14942,19 @@ export class PowerlevelCCTestNodeGet extends PowerlevelCC { // // @public (undocumented) export class PowerlevelCCTestNodeReport extends PowerlevelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (PowerlevelCCTestNodeReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) acknowledgedFrames: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): PowerlevelCCTestNodeReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) status: PowerlevelTestStatus; // (undocumented) testNodeId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "PowerlevelCCTestNodeReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -13960,23 +14973,25 @@ export interface PowerlevelCCTestNodeReportOptions { // // @public (undocumented) export class PowerlevelCCTestNodeSet extends PowerlevelCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | PowerlevelCCTestNodeSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): PowerlevelCCTestNodeSet; // (undocumented) powerlevel: Powerlevel; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) testFrameCount: number; // (undocumented) testNodeId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "PowerlevelCCTestNodeSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface PowerlevelCCTestNodeSetOptions extends CCCommandOptions { +export interface PowerlevelCCTestNodeSetOptions { // (undocumented) powerlevel: Powerlevel; // (undocumented) @@ -14022,9 +15037,9 @@ export class ProtectionCC extends CommandClass { // (undocumented) ccCommand: ProtectionCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ProtectionCCExclusiveControlGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -14037,30 +15052,42 @@ export class ProtectionCCExclusiveControlGet extends ProtectionCC { // // @public (undocumented) export class ProtectionCCExclusiveControlReport extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly exclusiveControlNodeId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + static from(raw: CCRaw, ctx: CCParsingContext_2): ProtectionCCExclusiveControlReport; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ProtectionCCExclusiveControlReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProtectionCCExclusiveControlReportOptions { + // (undocumented) + exclusiveControlNodeId: number; } // Warning: (ae-missing-release-tag) "ProtectionCCExclusiveControlSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ProtectionCCExclusiveControlSet extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ProtectionCCExclusiveControlSetOptions); + constructor(options: WithAddress); // (undocumented) exclusiveControlNodeId: number; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ProtectionCCExclusiveControlSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ProtectionCCExclusiveControlSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ProtectionCCExclusiveControlSetOptions extends CCCommandOptions { +export interface ProtectionCCExclusiveControlSetOptions { // (undocumented) exclusiveControlNodeId: number; } @@ -14075,34 +15102,48 @@ export class ProtectionCCGet extends ProtectionCC { // // @public (undocumented) export class ProtectionCCReport extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ProtectionCCReport; // (undocumented) readonly local: LocalProtectionState; // (undocumented) readonly rf?: RFProtectionState; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ProtectionCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProtectionCCReportOptions { + // (undocumented) + local: LocalProtectionState; + // (undocumented) + rf?: RFProtectionState; } // Warning: (ae-missing-release-tag) "ProtectionCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ProtectionCCSet extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ProtectionCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ProtectionCCSet; // (undocumented) local: LocalProtectionState; // (undocumented) rf?: RFProtectionState; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ProtectionCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ProtectionCCSetOptions extends CCCommandOptions { +export interface ProtectionCCSetOptions { // (undocumented) local: LocalProtectionState; // (undocumented) @@ -14119,9 +15160,11 @@ export class ProtectionCCSupportedGet extends ProtectionCC { // // @public (undocumented) export class ProtectionCCSupportedReport extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): ProtectionCCSupportedReport; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly supportedLocalStates: LocalProtectionState[]; // (undocumented) @@ -14131,7 +15174,21 @@ export class ProtectionCCSupportedReport extends ProtectionCC { // (undocumented) readonly supportsTimeout: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ProtectionCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProtectionCCSupportedReportOptions { + // (undocumented) + supportedLocalStates: LocalProtectionState[]; + // (undocumented) + supportedRFStates: RFProtectionState[]; + // (undocumented) + supportsExclusiveControl: boolean; + // (undocumented) + supportsTimeout: boolean; } // Warning: (ae-missing-release-tag) "ProtectionCCTimeoutGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -14144,30 +15201,42 @@ export class ProtectionCCTimeoutGet extends ProtectionCC { // // @public (undocumented) export class ProtectionCCTimeoutReport extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ProtectionCCTimeoutReport; // (undocumented) readonly timeout: Timeout; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ProtectionCCTimeoutReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProtectionCCTimeoutReportOptions { + // (undocumented) + timeout: Timeout; } // Warning: (ae-missing-release-tag) "ProtectionCCTimeoutSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ProtectionCCTimeoutSet extends ProtectionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ProtectionCCTimeoutSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ProtectionCCTimeoutSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) timeout: Timeout; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ProtectionCCTimeoutSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ProtectionCCTimeoutSetOptions extends CCCommandOptions { +export interface ProtectionCCTimeoutSetOptions { // (undocumented) timeout: Timeout; } @@ -14193,11 +15262,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -14218,11 +15287,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -14243,11 +15312,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -14268,11 +15337,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -14296,11 +15365,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -14325,11 +15394,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -14382,11 +15451,11 @@ export const ProtectionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -14432,10 +15501,15 @@ export enum RateType { Unspecified = 0 } +// Warning: (ae-missing-release-tag) "RefreshValuesContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type RefreshValuesContext = CCAPIHost>; + // Warning: (ae-missing-release-tag) "removeAssociations" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -function removeAssociations(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, group: number, destinations: AssociationAddress[]): Promise; +function removeAssociations(ctx: CCAPIHost, endpoint: EndpointId_2 & SupportsCC & ControlsCC, group: number, destinations: AssociationAddress[]): Promise; // Warning: (ae-missing-release-tag) "ReturnWithTXReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -14466,21 +15540,23 @@ export class SceneActivationCC extends CommandClass { // // @public (undocumented) export class SceneActivationCCSet extends SceneActivationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SceneActivationCCSetOptions); + constructor(options: WithAddress); // (undocumented) dimmingDuration: Duration | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SceneActivationCCSet; + // (undocumented) sceneId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SceneActivationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SceneActivationCCSetOptions extends CCCommandOptions { +export interface SceneActivationCCSetOptions { // (undocumented) dimmingDuration?: Duration | string; // (undocumented) @@ -14538,11 +15614,11 @@ export const SceneActivationCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly secret: false; readonly internal: false; - readonly minVersion: 1; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly secret: false; + readonly minVersion: 1; readonly stateful: false; }; }; @@ -14563,26 +15639,28 @@ export class SceneActuatorConfigurationCC extends CommandClass { // (undocumented) ccCommand: SceneActuatorConfigurationCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "SceneActuatorConfigurationCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SceneActuatorConfigurationCCGet extends SceneActuatorConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SceneActuatorConfigurationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): SceneActuatorConfigurationCCGet; // (undocumented) sceneId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SceneActuatorConfigurationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SceneActuatorConfigurationCCGetOptions extends CCCommandOptions { +export interface SceneActuatorConfigurationCCGetOptions { // (undocumented) sceneId: number; } @@ -14591,40 +15669,56 @@ export interface SceneActuatorConfigurationCCGetOptions extends CCCommandOptions // // @public (undocumented) export class SceneActuatorConfigurationCCReport extends SceneActuatorConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly dimmingDuration?: Duration; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SceneActuatorConfigurationCCReport; + // (undocumented) readonly level?: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly sceneId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "SceneActuatorConfigurationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface SceneActuatorConfigurationCCReportOptions { + // (undocumented) + dimmingDuration?: Duration; + // (undocumented) + level?: number; + // (undocumented) + sceneId: number; } // Warning: (ae-missing-release-tag) "SceneActuatorConfigurationCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SceneActuatorConfigurationCCSet extends SceneActuatorConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SceneActuatorConfigurationCCSetOptions); + constructor(options: WithAddress); // (undocumented) dimmingDuration: Duration; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): SceneActuatorConfigurationCCSet; + // (undocumented) level?: number; // (undocumented) sceneId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SceneActuatorConfigurationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SceneActuatorConfigurationCCSetOptions extends CCCommandOptions { +export interface SceneActuatorConfigurationCCSetOptions { // (undocumented) dimmingDuration: Duration; // (undocumented) @@ -14638,12 +15732,6 @@ export interface SceneActuatorConfigurationCCSetOptions extends CCCommandOptions // @public (undocumented) export const SceneActuatorConfigurationCCValues: Readonly<{ dimmingDuration: ((sceneId: number) => { - readonly meta: { - readonly label: `Dimming duration (${number})`; - readonly type: "duration"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Scene Actuator Configuration"]; property: "dimmingDuration"; @@ -14655,6 +15743,12 @@ export const SceneActuatorConfigurationCCValues: Readonly<{ readonly property: "dimmingDuration"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Dimming duration (${number})`; + readonly type: "duration"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -14667,15 +15761,6 @@ export const SceneActuatorConfigurationCCValues: Readonly<{ }; }; level: ((sceneId: number) => { - readonly meta: { - readonly label: `Level (${number})`; - readonly valueChangeOptions: readonly ["transitionDuration"]; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Scene Actuator Configuration"]; property: "level"; @@ -14687,6 +15772,15 @@ export const SceneActuatorConfigurationCCValues: Readonly<{ readonly property: "level"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Level (${number})`; + readonly valueChangeOptions: readonly ["transitionDuration"]; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -14720,30 +15814,32 @@ export class SceneControllerConfigurationCC extends CommandClass { ccCommand: SceneControllerConfigurationCommand; // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; - static getGroupCountCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): number; + static getGroupCountCached(ctx: GetValueDB_2 & GetDeviceConfig_2, endpoint: EndpointId_2): number; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "SceneControllerConfigurationCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SceneControllerConfigurationCCGet extends SceneControllerConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SceneControllerConfigurationCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): SceneControllerConfigurationCCGet; // (undocumented) groupId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SceneControllerConfigurationCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SceneControllerConfigurationCCGetOptions extends CCCommandOptions { +export interface SceneControllerConfigurationCCGetOptions { // (undocumented) groupId: number; } @@ -14752,40 +15848,56 @@ export interface SceneControllerConfigurationCCGetOptions extends CCCommandOptio // // @public (undocumented) export class SceneControllerConfigurationCCReport extends SceneControllerConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly dimmingDuration: Duration; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SceneControllerConfigurationCCReport; + // (undocumented) readonly groupId: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly sceneId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "SceneControllerConfigurationCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface SceneControllerConfigurationCCReportOptions { + // (undocumented) + dimmingDuration: Duration; + // (undocumented) + groupId: number; + // (undocumented) + sceneId: number; } // Warning: (ae-missing-release-tag) "SceneControllerConfigurationCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SceneControllerConfigurationCCSet extends SceneControllerConfigurationCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SceneControllerConfigurationCCSetOptions); + constructor(options: WithAddress); // (undocumented) dimmingDuration: Duration; // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): SceneControllerConfigurationCCSet; + // (undocumented) groupId: number; // (undocumented) sceneId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SceneControllerConfigurationCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SceneControllerConfigurationCCSetOptions extends CCCommandOptions { +export interface SceneControllerConfigurationCCSetOptions { // (undocumented) dimmingDuration?: Duration | string; // (undocumented) @@ -14799,12 +15911,6 @@ export interface SceneControllerConfigurationCCSetOptions extends CCCommandOptio // @public (undocumented) export const SceneControllerConfigurationCCValues: Readonly<{ dimmingDuration: ((groupId: number) => { - readonly meta: { - readonly label: `Dimming duration (${number})`; - readonly type: "duration"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Scene Controller Configuration"]; property: "dimmingDuration"; @@ -14816,6 +15922,12 @@ export const SceneControllerConfigurationCCValues: Readonly<{ readonly property: "dimmingDuration"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Dimming duration (${number})`; + readonly type: "duration"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -14828,15 +15940,6 @@ export const SceneControllerConfigurationCCValues: Readonly<{ }; }; sceneId: ((groupId: number) => { - readonly meta: { - readonly label: `Associated Scene ID (${number})`; - readonly valueChangeOptions: readonly ["transitionDuration"]; - readonly min: 0; - readonly max: 255; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Scene Controller Configuration"]; property: "sceneId"; @@ -14848,6 +15951,15 @@ export const SceneControllerConfigurationCCValues: Readonly<{ readonly property: "sceneId"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `Associated Scene ID (${number})`; + readonly valueChangeOptions: readonly ["transitionDuration"]; + readonly min: 0; + readonly max: 255; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -14879,34 +15991,36 @@ export enum SceneControllerConfigurationCommand { export class ScheduleEntryLockCC extends CommandClass { // (undocumented) ccCommand: ScheduleEntryLockCommand; - static getNumDailyRepeatingSlotsCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint): number; - static getNumWeekDaySlotsCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint): number; - static getNumYearDaySlotsCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint): number; + static getNumDailyRepeatingSlotsCached(ctx: GetValueDB, endpoint: EndpointId_2): number; + static getNumWeekDaySlotsCached(ctx: GetValueDB, endpoint: EndpointId_2): number; + static getNumYearDaySlotsCached(ctx: GetValueDB, endpoint: EndpointId_2): number; // (undocumented) - static getScheduleCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, scheduleKind: ScheduleEntryLockScheduleKind.WeekDay, userId: number, slotId: number): MaybeNotKnown; + static getScheduleCached(ctx: GetValueDB, endpoint: EndpointId_2, scheduleKind: ScheduleEntryLockScheduleKind.WeekDay, userId: number, slotId: number): MaybeNotKnown; // (undocumented) - static getScheduleCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, scheduleKind: ScheduleEntryLockScheduleKind.YearDay, userId: number, slotId: number): MaybeNotKnown; + static getScheduleCached(ctx: GetValueDB, endpoint: EndpointId_2, scheduleKind: ScheduleEntryLockScheduleKind.YearDay, userId: number, slotId: number): MaybeNotKnown; // (undocumented) - static getScheduleCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, scheduleKind: ScheduleEntryLockScheduleKind.DailyRepeating, userId: number, slotId: number): MaybeNotKnown; + static getScheduleCached(ctx: GetValueDB, endpoint: EndpointId_2, scheduleKind: ScheduleEntryLockScheduleKind.DailyRepeating, userId: number, slotId: number): MaybeNotKnown; // (undocumented) - static getScheduleCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number): MaybeNotKnown; - static getUserCodeScheduleEnabledCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, userId: number): boolean; - static getUserCodeScheduleKindCached(applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint, userId: number): MaybeNotKnown; + static getScheduleCached(ctx: GetValueDB, endpoint: EndpointId_2, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number): MaybeNotKnown; + static getUserCodeScheduleEnabledCached(ctx: GetValueDB, endpoint: EndpointId_2, userId: number): boolean; + static getUserCodeScheduleKindCached(ctx: GetValueDB, endpoint: EndpointId_2, userId: number): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCDailyRepeatingScheduleGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCDailyRepeatingScheduleGet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCDailyRepeatingScheduleGetOptions); + constructor(options: WithAddress_2); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCDailyRepeatingScheduleGet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -14914,21 +16028,23 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleGet extends ScheduleEntryL // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCDailyRepeatingScheduleGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCDailyRepeatingScheduleGetOptions = CCCommandOptions & ScheduleEntryLockSlotId; +export type ScheduleEntryLockCCDailyRepeatingScheduleGetOptions = ScheduleEntryLockSlotId; // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCDailyRepeatingScheduleReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCDailyRepeatingScheduleReport extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | (CCCommandOptions & ScheduleEntryLockCCDailyRepeatingScheduleReportOptions)); + constructor(options: WithAddress_2); // (undocumented) durationHour?: number; // (undocumented) durationMinute?: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost): boolean; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCDailyRepeatingScheduleReport; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -14936,7 +16052,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport extends ScheduleEnt // (undocumented) startMinute?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; // (undocumented) @@ -14952,7 +16068,7 @@ export type ScheduleEntryLockCCDailyRepeatingScheduleReportOptions = ScheduleEnt // // @public (undocumented) export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCDailyRepeatingScheduleSetOptions); + constructor(options: WithAddress_2); // (undocumented) action: ScheduleEntryLockSetAction; // (undocumented) @@ -14960,7 +16076,9 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryL // (undocumented) durationMinute?: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCDailyRepeatingScheduleSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -14968,7 +16086,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryL // (undocumented) startMinute?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; // (undocumented) @@ -14979,7 +16097,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryL // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCDailyRepeatingScheduleSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCDailyRepeatingScheduleSetOptions = CCCommandOptions & ScheduleEntryLockSlotId & ({ +export type ScheduleEntryLockCCDailyRepeatingScheduleSetOptions = ScheduleEntryLockSlotId & ({ action: ScheduleEntryLockSetAction.Erase; } | ({ action: ScheduleEntryLockSetAction.Set; @@ -14989,19 +16107,21 @@ export type ScheduleEntryLockCCDailyRepeatingScheduleSetOptions = CCCommandOptio // // @public (undocumented) export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCEnableAllSetOptions); + constructor(options: WithAddress_2); // (undocumented) enabled: boolean; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCEnableAllSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCEnableAllSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ScheduleEntryLockCCEnableAllSetOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCEnableAllSetOptions { // (undocumented) enabled: boolean; } @@ -15010,13 +16130,15 @@ export interface ScheduleEntryLockCCEnableAllSetOptions extends CCCommandOptions // // @public (undocumented) export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCEnableSetOptions); + constructor(options: WithAddress_2); // (undocumented) enabled: boolean; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCEnableSet; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + serialize(ctx: CCEncodingContext): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -15024,7 +16146,7 @@ export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCEnableSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ScheduleEntryLockCCEnableSetOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCEnableSetOptions { // (undocumented) enabled: boolean; // (undocumented) @@ -15041,7 +16163,9 @@ export class ScheduleEntryLockCCSupportedGet extends ScheduleEntryLockCC { // // @public (undocumented) export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCSupportedReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCSupportedReport; // (undocumented) numDailyRepeatingSlots: number | undefined; // (undocumented) @@ -15049,15 +16173,15 @@ export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { // (undocumented) numYearDaySlots: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ScheduleEntryLockCCSupportedReportOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCSupportedReportOptions { // (undocumented) numDailyRepeatingSlots?: number; // (undocumented) @@ -15076,21 +16200,23 @@ export class ScheduleEntryLockCCTimeOffsetGet extends ScheduleEntryLockCC { // // @public (undocumented) export class ScheduleEntryLockCCTimeOffsetReport extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCTimeOffsetReportOptions); + constructor(options: WithAddress_2); // (undocumented) dstOffset: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCTimeOffsetReport; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) standardOffset: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCTimeOffsetReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ScheduleEntryLockCCTimeOffsetReportOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCTimeOffsetReportOptions { // (undocumented) dstOffset: number; // (undocumented) @@ -15101,21 +16227,23 @@ export interface ScheduleEntryLockCCTimeOffsetReportOptions extends CCCommandOpt // // @public (undocumented) export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCTimeOffsetSetOptions); + constructor(options: WithAddress_2); // (undocumented) dstOffset: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCTimeOffsetSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) standardOffset: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCTimeOffsetSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ScheduleEntryLockCCTimeOffsetSetOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCTimeOffsetSetOptions { // (undocumented) dstOffset: number; // (undocumented) @@ -15127,11 +16255,6 @@ export interface ScheduleEntryLockCCTimeOffsetSetOptions extends CCCommandOption // @public (undocumented) export const ScheduleEntryLockCCValues: Readonly<{ schedule: ((scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Schedule Entry Lock"]; property: "schedule"; @@ -15143,23 +16266,23 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly property: "schedule"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; scheduleKind: ((userId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Schedule Entry Lock"]; property: "scheduleKind"; @@ -15171,23 +16294,23 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly property: "scheduleKind"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; userEnabled: ((userId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Schedule Entry Lock"]; property: "userEnabled"; @@ -15199,14 +16322,19 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly property: "userEnabled"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -15227,11 +16355,11 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -15252,11 +16380,11 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -15277,11 +16405,11 @@ export const ScheduleEntryLockCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -15291,13 +16419,15 @@ export const ScheduleEntryLockCCValues: Readonly<{ // // @public (undocumented) export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleGetOptions); + constructor(options: WithAddress_2); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCWeekDayScheduleGet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -15305,17 +16435,19 @@ export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCWeekDayScheduleGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCWeekDayScheduleGetOptions = CCCommandOptions & ScheduleEntryLockSlotId; +export type ScheduleEntryLockCCWeekDayScheduleGetOptions = ScheduleEntryLockSlotId; // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCWeekDayScheduleReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCWeekDayScheduleReport extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCWeekDayScheduleReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -15327,7 +16459,7 @@ export class ScheduleEntryLockCCWeekDayScheduleReport extends ScheduleEntryLockC // (undocumented) stopMinute?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; // (undocumented) @@ -15337,17 +16469,19 @@ export class ScheduleEntryLockCCWeekDayScheduleReport extends ScheduleEntryLockC // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCWeekDayScheduleReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCWeekDayScheduleReportOptions = CCCommandOptions & ScheduleEntryLockSlotId & AllOrNone_2; +export type ScheduleEntryLockCCWeekDayScheduleReportOptions = ScheduleEntryLockSlotId & AllOrNone_2; // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCWeekDayScheduleSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCWeekDayScheduleSetOptions); + constructor(options: WithAddress_2); // (undocumented) action: ScheduleEntryLockSetAction; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCWeekDayScheduleSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -15359,7 +16493,7 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { // (undocumented) stopMinute?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; // (undocumented) @@ -15370,7 +16504,7 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCWeekDayScheduleSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCWeekDayScheduleSetOptions = CCCommandOptions & ScheduleEntryLockSlotId & ({ +export type ScheduleEntryLockCCWeekDayScheduleSetOptions = ScheduleEntryLockSlotId & ({ action: ScheduleEntryLockSetAction.Erase; } | ({ action: ScheduleEntryLockSetAction.Set; @@ -15380,13 +16514,15 @@ export type ScheduleEntryLockCCWeekDayScheduleSetOptions = CCCommandOptions & Sc // // @public (undocumented) export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCYearDayScheduleGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -15394,17 +16530,19 @@ export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCYearDayScheduleGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCYearDayScheduleGetOptions = CCCommandOptions & ScheduleEntryLockSlotId; +export type ScheduleEntryLockCCYearDayScheduleGetOptions = ScheduleEntryLockSlotId; // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCYearDayScheduleReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCYearDayScheduleReport extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleReportOptions); + constructor(options: WithAddress_2); // (undocumented) - persistValues(applHost: ZWaveApplicationHost): boolean; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCYearDayScheduleReport; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -15428,7 +16566,7 @@ export class ScheduleEntryLockCCYearDayScheduleReport extends ScheduleEntryLockC // (undocumented) stopYear?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -15436,17 +16574,19 @@ export class ScheduleEntryLockCCYearDayScheduleReport extends ScheduleEntryLockC // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCYearDayScheduleReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCYearDayScheduleReportOptions = CCCommandOptions & ScheduleEntryLockSlotId & AllOrNone_2; +export type ScheduleEntryLockCCYearDayScheduleReportOptions = ScheduleEntryLockSlotId & AllOrNone_2; // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCYearDayScheduleSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ScheduleEntryLockCCYearDayScheduleSetOptions); + constructor(options: WithAddress_2); // (undocumented) action: ScheduleEntryLockSetAction; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ScheduleEntryLockCCYearDayScheduleSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) slotId: number; // (undocumented) @@ -15470,7 +16610,7 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { // (undocumented) stopYear?: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; // (undocumented) userId: number; } @@ -15479,7 +16619,7 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { // Warning: (ae-missing-release-tag) "ScheduleEntryLockCCYearDayScheduleSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ScheduleEntryLockCCYearDayScheduleSetOptions = CCCommandOptions & ScheduleEntryLockSlotId & ({ +export type ScheduleEntryLockCCYearDayScheduleSetOptions = ScheduleEntryLockSlotId & ({ action: ScheduleEntryLockSetAction.Erase; } | ({ action: ScheduleEntryLockSetAction.Set; @@ -15661,14 +16801,14 @@ export interface SchedulePollOptions { export class Security2CC extends CommandClass { // (undocumented) ccCommand: Security2Command; - static encapsulate(host: ZWaveHost_2, cc: CommandClass, options?: { + static encapsulate(cc: CommandClass, ownNodeId: number, securityManagers: SecurityManagers_2, options?: { securityClass?: SecurityClass; multicastOutOfSync?: boolean; multicastGroupId?: number; verifyDelivery?: boolean; }): Security2CCMessageEncapsulation; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; static requiresEncapsulation(cc: CommandClass): boolean; } @@ -15682,19 +16822,21 @@ export class Security2CCCommandsSupportedGet extends Security2CC { // // @public (undocumented) export class Security2CCCommandsSupportedReport extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCCommandsSupportedReportOptions); + constructor(options: WithAddress_2); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCCommandsSupportedReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedCCs: CommandClasses_2[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCCommandsSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCCommandsSupportedReportOptions extends CCCommandOptions { +export interface Security2CCCommandsSupportedReportOptions { // (undocumented) supportedCCs: CommandClasses_2[]; } @@ -15703,19 +16845,21 @@ export interface Security2CCCommandsSupportedReportOptions extends CCCommandOpti // // @public (undocumented) export class Security2CCKEXFail extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCKEXFailOptions); + constructor(options: WithAddress_2); // (undocumented) failType: KEXFailType; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCKEXFail; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCKEXFailOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCKEXFailOptions extends CCCommandOptions { +export interface Security2CCKEXFailOptions { // (undocumented) failType: KEXFailType; } @@ -15730,23 +16874,25 @@ export class Security2CCKEXGet extends Security2CC { // // @public (undocumented) export class Security2CCKEXReport extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & Security2CCKEXReportOptions)); + constructor(options: WithAddress_2); // (undocumented) readonly echo: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCKEXReport; + // (undocumented) readonly requestCSA: boolean; // (undocumented) readonly requestedKeys: readonly SecurityClass[]; // (undocumented) readonly _reserved: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedECDHProfiles: readonly ECDHProfiles[]; // (undocumented) readonly supportedKEXSchemes: readonly KEXSchemes[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCKEXReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -15771,10 +16917,12 @@ export interface Security2CCKEXReportOptions { // // @public (undocumented) export class Security2CCKEXSet extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & Security2CCKEXSetOptions)); + constructor(options: WithAddress_2); // (undocumented) echo: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCKEXSet; + // (undocumented) grantedKeys: SecurityClass[]; // (undocumented) permitCSA: boolean; @@ -15785,9 +16933,9 @@ export class Security2CCKEXSet extends Security2CC { // (undocumented) selectedKEXScheme: KEXSchemes; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCKEXSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -15812,7 +16960,7 @@ export interface Security2CCKEXSetOptions { // // @public (undocumented) export class Security2CCMessageEncapsulation extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCMessageEncapsulationOptions); + constructor(options: WithAddress_2); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) @@ -15820,6 +16968,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { // (undocumented) extensions: Security2Extension[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCMessageEncapsulation; getMulticastGroupId(): number | undefined; getSenderEI(): Buffer | undefined; // (undocumented) @@ -15828,11 +16977,12 @@ export class Security2CCMessageEncapsulation extends Security2CC { prepareRetransmission(): void; // (undocumented) readonly securityClass?: SecurityClass; - get sequenceNumber(): number; // (undocumented) - serialize(): Buffer; + sequenceNumber: number | undefined; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; // (undocumented) readonly verifyDelivery: boolean; } @@ -15840,13 +16990,15 @@ export class Security2CCMessageEncapsulation extends Security2CC { // Warning: (ae-missing-release-tag) "Security2CCMessageEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCMessageEncapsulationOptions extends CCCommandOptions { +export interface Security2CCMessageEncapsulationOptions { // (undocumented) encapsulated?: CommandClass; // (undocumented) extensions?: Security2Extension[]; securityClass?: SecurityClass; // (undocumented) + sequenceNumber?: number; + // (undocumented) verifyDelivery?: boolean; } @@ -15854,19 +17006,21 @@ export interface Security2CCMessageEncapsulationOptions extends CCCommandOptions // // @public (undocumented) export class Security2CCNetworkKeyGet extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCNetworkKeyGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCNetworkKeyGet; // (undocumented) requestedKey: SecurityClass; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCNetworkKeyGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCNetworkKeyGetOptions extends CCCommandOptions { +export interface Security2CCNetworkKeyGetOptions { // (undocumented) requestedKey: SecurityClass; } @@ -15875,21 +17029,23 @@ export interface Security2CCNetworkKeyGetOptions extends CCCommandOptions { // // @public (undocumented) export class Security2CCNetworkKeyReport extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCNetworkKeyReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCNetworkKeyReport; // (undocumented) grantedKey: SecurityClass; // (undocumented) networkKey: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCNetworkKeyReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCNetworkKeyReportOptions extends CCCommandOptions { +export interface Security2CCNetworkKeyReportOptions { // (undocumented) grantedKey: SecurityClass; // (undocumented) @@ -15906,36 +17062,52 @@ export class Security2CCNetworkKeyVerify extends Security2CC { // // @public (undocumented) export class Security2CCNonceGet extends Security2CC { - constructor(host: ZWaveHost_2, options: CCCommandOptions); - get sequenceNumber(): number; + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCNonceGet; + // (undocumented) + sequenceNumber: number | undefined; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - serialize(): Buffer; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; +} + +// Warning: (ae-missing-release-tag) "Security2CCNonceGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Security2CCNonceGetOptions { // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + sequenceNumber?: number; } // Warning: (ae-missing-release-tag) "Security2CCNonceReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class Security2CCNonceReport extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & Security2CCNonceReportOptions)); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCNonceReport; // (undocumented) readonly MOS: boolean; // (undocumented) readonly receiverEI?: Buffer; - get sequenceNumber(): number; // (undocumented) - serialize(): Buffer; + sequenceNumber: number | undefined; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly SOS: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCNonceReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export type Security2CCNonceReportOptions = { + sequenceNumber?: number; +} & ({ MOS: boolean; SOS: true; receiverEI: Buffer; @@ -15943,27 +17115,29 @@ export type Security2CCNonceReportOptions = { MOS: true; SOS: false; receiverEI?: undefined; -}; +}); // Warning: (ae-missing-release-tag) "Security2CCPublicKeyReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class Security2CCPublicKeyReport extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCPublicKeyReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCPublicKeyReport; // (undocumented) includingNode: boolean; // (undocumented) publicKey: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCPublicKeyReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCPublicKeyReportOptions extends CCCommandOptions { +export interface Security2CCPublicKeyReportOptions { // (undocumented) includingNode: boolean; // (undocumented) @@ -15974,21 +17148,23 @@ export interface Security2CCPublicKeyReportOptions extends CCCommandOptions { // // @public (undocumented) export class Security2CCTransferEnd extends Security2CC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | Security2CCTransferEndOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): Security2CCTransferEnd; // (undocumented) keyRequestComplete: boolean; // (undocumented) keyVerified: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "Security2CCTransferEndOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface Security2CCTransferEndOptions extends CCCommandOptions { +export interface Security2CCTransferEndOptions { // (undocumented) keyRequestComplete: boolean; // (undocumented) @@ -16064,13 +17240,9 @@ export class Security2Extension { export class SecurityCC extends CommandClass { // (undocumented) ccCommand: SecurityCommand; - static encapsulate(host: ZWaveHost_2, cc: CommandClass): SecurityCCCommandEncapsulation; + static encapsulate(ownNodeId: number, securityManager: SecurityManager, cc: CommandClass): SecurityCCCommandEncapsulation; // (undocumented) - host: ZWaveHost_2 & { - securityManager: SecurityManager; - }; - // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) nodeId: number; static requiresEncapsulation(cc: CommandClass): boolean; @@ -16080,7 +17252,7 @@ export class SecurityCC extends CommandClass { // // @public (undocumented) export class SecurityCCCommandEncapsulation extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SecurityCCCommandEncapsulationOptions); + constructor(options: WithAddress_2); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) @@ -16088,17 +17260,19 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SecurityCCCommandEncapsulation; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: SecurityCCCommandEncapsulation[]): void; + mergePartialCCs(partials: SecurityCCCommandEncapsulation[], ctx: CCParsingContext_2): void; // (undocumented) nonce: Buffer | undefined; // (undocumented) get nonceId(): number | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCCommandEncapsulationNonceGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -16110,12 +17284,16 @@ export class SecurityCCCommandEncapsulationNonceGet extends SecurityCCCommandEnc // Warning: (ae-missing-release-tag) "SecurityCCCommandEncapsulationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SecurityCCCommandEncapsulationOptions extends CCCommandOptions { - // (undocumented) +export type SecurityCCCommandEncapsulationOptions = { alternativeNetworkKey?: Buffer; - // (undocumented) +} & ({ encapsulated: CommandClass; -} +} | { + decryptedCCBytes: Buffer; + sequenced: boolean; + secondFrame: boolean; + sequenceCounter: number; +}); // Warning: (ae-missing-release-tag) "SecurityCCCommandsSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -16127,32 +17305,36 @@ export class SecurityCCCommandsSupportedGet extends SecurityCC { // // @public (undocumented) export class SecurityCCCommandsSupportedReport extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SecurityCCCommandsSupportedReportOptions); + constructor(options: WithAddress_2); // (undocumented) controlledCCs: CommandClasses_2[]; // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SecurityCCCommandsSupportedReport; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: SecurityCCCommandsSupportedReport[]): void; + mergePartialCCs(partials: SecurityCCCommandsSupportedReport[]): void; // (undocumented) - readonly reportsToFollow: number; + reportsToFollow: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportedCCs: CommandClasses_2[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCCommandsSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SecurityCCCommandsSupportedReportOptions extends CCCommandOptions { +export interface SecurityCCCommandsSupportedReportOptions { // (undocumented) controlledCCs: CommandClasses_2[]; // (undocumented) + reportsToFollow?: number; + // (undocumented) supportedCCs: CommandClasses_2[]; } @@ -16160,19 +17342,21 @@ export interface SecurityCCCommandsSupportedReportOptions extends CCCommandOptio // // @public (undocumented) export class SecurityCCNetworkKeySet extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SecurityCCNetworkKeySetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SecurityCCNetworkKeySet; // (undocumented) networkKey: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(applHost: ZWaveApplicationHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCNetworkKeySetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SecurityCCNetworkKeySetOptions extends CCCommandOptions { +export interface SecurityCCNetworkKeySetOptions { // (undocumented) networkKey: Buffer; } @@ -16194,46 +17378,47 @@ export class SecurityCCNonceGet extends SecurityCC { // @public (undocumented) export class SecurityCCNonceReport extends SecurityCC { // Warning: (ae-forgotten-export) The symbol "SecurityCCNonceReportOptions" needs to be exported by the entry point index.d.ts - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SecurityCCNonceReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SecurityCCNonceReport; // (undocumented) nonce: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCSchemeGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SecurityCCSchemeGet extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCSchemeInherit" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SecurityCCSchemeInherit extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCCSchemeReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SecurityCCSchemeReport extends SecurityCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | CCCommandOptions); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): SecurityCCSchemeReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "SecurityCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -16394,7 +17579,7 @@ export class SoundSwitchCC extends CommandClass { // (undocumented) ccCommand: SoundSwitchCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "SoundSwitchCCConfigurationGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -16407,21 +17592,23 @@ export class SoundSwitchCCConfigurationGet extends SoundSwitchCC { // // @public (undocumented) export class SoundSwitchCCConfigurationReport extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCConfigurationReportOptions); + constructor(options: WithAddress); // (undocumented) defaultToneId: number; // (undocumented) defaultVolume: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCConfigurationReport; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SoundSwitchCCConfigurationReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCConfigurationReportOptions extends CCCommandOptions { +export interface SoundSwitchCCConfigurationReportOptions { // (undocumented) defaultToneId: number; // (undocumented) @@ -16432,21 +17619,23 @@ export interface SoundSwitchCCConfigurationReportOptions extends CCCommandOption // // @public (undocumented) export class SoundSwitchCCConfigurationSet extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCConfigurationSetOptions); + constructor(options: WithAddress); // (undocumented) defaultToneId: number; // (undocumented) defaultVolume: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCConfigurationSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SoundSwitchCCConfigurationSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCConfigurationSetOptions extends CCCommandOptions { +export interface SoundSwitchCCConfigurationSetOptions { // (undocumented) defaultToneId: number; // (undocumented) @@ -16457,11 +17646,13 @@ export interface SoundSwitchCCConfigurationSetOptions extends CCCommandOptions { // // @public (undocumented) export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCToneInfoGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCToneInfoGet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) toneId: number; } @@ -16469,7 +17660,7 @@ export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { // Warning: (ae-missing-release-tag) "SoundSwitchCCToneInfoGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCToneInfoGetOptions extends CCCommandOptions { +export interface SoundSwitchCCToneInfoGetOptions { // (undocumented) toneId: number; } @@ -16478,15 +17669,17 @@ export interface SoundSwitchCCToneInfoGetOptions extends CCCommandOptions { // // @public (undocumented) export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCToneInfoReportOptions); + constructor(options: WithAddress); // (undocumented) readonly duration: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCToneInfoReport; + // (undocumented) readonly name: string; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly toneId: number; } @@ -16494,7 +17687,7 @@ export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { // Warning: (ae-missing-release-tag) "SoundSwitchCCToneInfoReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCToneInfoReportOptions extends CCCommandOptions { +export interface SoundSwitchCCToneInfoReportOptions { // (undocumented) duration: number; // (undocumented) @@ -16513,24 +17706,40 @@ export class SoundSwitchCCTonePlayGet extends SoundSwitchCC { // // @public (undocumented) export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCTonePlayReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly toneId: ToneId | number; // (undocumented) volume?: number; } +// Warning: (ae-missing-release-tag) "SoundSwitchCCTonePlayReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface SoundSwitchCCTonePlayReportOptions { + // (undocumented) + toneId: ToneId | number; + // (undocumented) + volume?: number; +} + // Warning: (ae-missing-release-tag) "SoundSwitchCCTonePlaySet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCTonePlaySetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCTonePlaySet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) toneId: ToneId | number; // (undocumented) @@ -16540,7 +17749,7 @@ export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { // Warning: (ae-missing-release-tag) "SoundSwitchCCTonePlaySetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCTonePlaySetOptions extends CCCommandOptions { +export interface SoundSwitchCCTonePlaySetOptions { // (undocumented) toneId: ToneId | number; // (undocumented) @@ -16557,11 +17766,13 @@ export class SoundSwitchCCTonesNumberGet extends SoundSwitchCC { // // @public (undocumented) export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SoundSwitchCCTonesNumberReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SoundSwitchCCTonesNumberReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) toneCount: number; } @@ -16569,7 +17780,7 @@ export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { // Warning: (ae-missing-release-tag) "SoundSwitchCCTonesNumberReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SoundSwitchCCTonesNumberReportOptions extends CCCommandOptions { +export interface SoundSwitchCCTonesNumberReportOptions { // (undocumented) toneCount: number; } @@ -16590,7 +17801,7 @@ export const SoundSwitchCCValues: Readonly<{ }; readonly is: (valueId: ValueID_2) => boolean; readonly meta: { - readonly min: 0; + readonly min: 1; readonly max: 254; readonly label: "Default tone ID"; readonly type: "number"; @@ -16767,64 +17978,70 @@ export enum SubsystemType { export class SupervisionCC extends CommandClass { // (undocumented) ccCommand: SupervisionCommand; - static encapsulate(host: ZWaveHost_2, cc: CommandClass, requestStatusUpdates?: boolean): SupervisionCCGet; - static getCCSupportedWithSupervision(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, ccId: CommandClasses): boolean; + static encapsulate(cc: CommandClass, sessionId: number, requestStatusUpdates?: boolean): SupervisionCCGet; + static getCCSupportedWithSupervision(ctx: GetValueDB_2, endpoint: EndpointId_2, ccId: CommandClasses): boolean; static getSessionId(command: CommandClass): number | undefined; - static mayUseSupervision(applHost: ZWaveApplicationHost_2, command: T): command is SinglecastCC_2; + static mayUseSupervision(ctx: GetValueDB_2 & GetNode_2>, command: T): command is SinglecastCC_2; // (undocumented) nodeId: number; static requiresEncapsulation(cc: CommandClass): boolean; - static setCCSupportedWithSupervision(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, ccId: CommandClasses, supported: boolean): void; + static setCCSupportedWithSupervision(ctx: GetValueDB_2, endpoint: EndpointId_2, ccId: CommandClasses, supported: boolean): void; } // Warning: (ae-missing-release-tag) "SupervisionCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SupervisionCCGet extends SupervisionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | SupervisionCCGetOptions); + constructor(options: WithAddress); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) encapsulated: CommandClass; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SupervisionCCGet; + // (undocumented) requestStatusUpdates: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) sessionId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "SupervisionCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface SupervisionCCGetOptions extends CCCommandOptions { +export interface SupervisionCCGetOptions { // (undocumented) encapsulated: CommandClass; // (undocumented) requestStatusUpdates: boolean; + // (undocumented) + sessionId: number; } // Warning: (ae-missing-release-tag) "SupervisionCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class SupervisionCCReport extends SupervisionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & SupervisionCCReportOptions)); + constructor(options: WithAddress); // (undocumented) readonly duration: Duration | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): SupervisionCCReport; + // (undocumented) readonly moreUpdatesFollow: boolean; // (undocumented) readonly requestWakeUpOnDemand: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly sessionId: number; // (undocumented) readonly status: SupervisionStatus; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) toSupervisionResult(): SupervisionResult; } @@ -16848,11 +18065,6 @@ export type SupervisionCCReportOptions = { // @public (undocumented) export const SupervisionCCValues: Readonly<{ ccSupported: ((ccId: CommandClasses) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: CommandClasses.Supervision; property: "ccSupported"; @@ -16864,13 +18076,18 @@ export const SupervisionCCValues: Readonly<{ readonly property: "ccSupported"; readonly propertyKey: CommandClasses; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; }; @@ -16963,9 +18180,9 @@ export class ThermostatFanModeCC extends CommandClass { // (undocumented) ccCommand: ThermostatFanModeCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ThermostatFanModeCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -16978,37 +18195,53 @@ export class ThermostatFanModeCCGet extends ThermostatFanModeCC { // // @public (undocumented) export class ThermostatFanModeCCReport extends ThermostatFanModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatFanModeCCReport; // (undocumented) readonly mode: ThermostatFanMode; // (undocumented) readonly off: boolean | undefined; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ThermostatFanModeCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ThermostatFanModeCCReportOptions { + // (undocumented) + mode: ThermostatFanMode; + // (undocumented) + off?: boolean; } // Warning: (ae-missing-release-tag) "ThermostatFanModeCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ThermostatFanModeCCSet extends ThermostatFanModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatFanModeCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): ThermostatFanModeCCSet; // (undocumented) mode: ThermostatFanMode; // (undocumented) off: boolean | undefined; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatFanModeCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ThermostatFanModeCCSetOptions = CCCommandOptions & { +export interface ThermostatFanModeCCSetOptions { + // (undocumented) mode: ThermostatFanMode; + // (undocumented) off?: boolean; -}; +} // Warning: (ae-missing-release-tag) "ThermostatFanModeCCSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -17020,13 +18253,23 @@ export class ThermostatFanModeCCSupportedGet extends ThermostatFanModeCC { // // @public (undocumented) export class ThermostatFanModeCCSupportedReport extends ThermostatFanModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatFanModeCCSupportedReport; + // (undocumented) + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) readonly supportedModes: ThermostatFanMode[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ThermostatFanModeCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ThermostatFanModeCCSupportedReportOptions { + // (undocumented) + supportedModes: ThermostatFanMode[]; } // Warning: (ae-missing-release-tag) "ThermostatFanModeCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17050,11 +18293,11 @@ export const ThermostatFanModeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -17107,11 +18350,11 @@ export const ThermostatFanModeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; }; }; @@ -17164,9 +18407,9 @@ export class ThermostatFanStateCC extends CommandClass { // (undocumented) ccCommand: ThermostatFanStateCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ThermostatFanStateCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17179,11 +18422,21 @@ export class ThermostatFanStateCCGet extends ThermostatFanStateCC { // // @public (undocumented) export class ThermostatFanStateCCReport extends ThermostatFanStateCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatFanStateCCReport; // (undocumented) readonly state: ThermostatFanState; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ThermostatFanStateCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ThermostatFanStateCCReportOptions { + // (undocumented) + state: ThermostatFanState; } // Warning: (ae-missing-release-tag) "ThermostatFanStateCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17278,9 +18531,9 @@ export class ThermostatModeCC extends CommandClass { // (undocumented) ccCommand: ThermostatModeCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ThermostatModeCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17293,54 +18546,58 @@ export class ThermostatModeCCGet extends ThermostatModeCC { // // @public (undocumented) export class ThermostatModeCCReport extends ThermostatModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatModeCCReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatModeCCReport; // (undocumented) readonly manufacturerData: Buffer | undefined; // (undocumented) readonly mode: ThermostatMode; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatModeCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ThermostatModeCCReportOptions = CCCommandOptions & ({ +export type ThermostatModeCCReportOptions = { mode: Exclude; manufacturerData?: undefined; } | { mode: (typeof ThermostatMode)["Manufacturer specific"]; manufacturerData?: Buffer; -}); +}; // Warning: (ae-missing-release-tag) "ThermostatModeCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ThermostatModeCCSet extends ThermostatModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatModeCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatModeCCSet; // (undocumented) manufacturerData?: Buffer; // (undocumented) mode: ThermostatMode; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatModeCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ThermostatModeCCSetOptions = CCCommandOptions & ({ +export type ThermostatModeCCSetOptions = { mode: Exclude; } | { mode: (typeof ThermostatMode)["Manufacturer specific"]; manufacturerData: Buffer; -}); +}; // Warning: (ae-missing-release-tag) "ThermostatModeCCSupportedGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -17352,21 +18609,23 @@ export class ThermostatModeCCSupportedGet extends ThermostatModeCC { // // @public (undocumented) export class ThermostatModeCCSupportedReport extends ThermostatModeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatModeCCSupportedReportOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatModeCCSupportedReport; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedModes: ThermostatMode[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatModeCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatModeCCSupportedReportOptions extends CCCommandOptions { +export interface ThermostatModeCCSupportedReportOptions { // (undocumented) supportedModes: ThermostatMode[]; } @@ -17392,11 +18651,11 @@ export const ThermostatModeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -17512,9 +18771,9 @@ export class ThermostatOperatingStateCC extends CommandClass { // (undocumented) ccCommand: ThermostatOperatingStateCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ThermostatOperatingStateCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17527,11 +18786,21 @@ export class ThermostatOperatingStateCCGet extends ThermostatOperatingStateCC { // // @public (undocumented) export class ThermostatOperatingStateCCReport extends ThermostatOperatingStateCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatOperatingStateCCReport; // (undocumented) readonly state: ThermostatOperatingState; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ThermostatOperatingStateCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ThermostatOperatingStateCCReportOptions { + // (undocumented) + state: ThermostatOperatingState; } // Warning: (ae-missing-release-tag) "ThermostatOperatingStateCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17588,9 +18857,9 @@ export class ThermostatSetbackCC extends CommandClass { // (undocumented) ccCommand: ThermostatSetbackCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; } // Warning: (ae-missing-release-tag) "ThermostatSetbackCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -17602,100 +18871,55 @@ export class ThermostatSetbackCCGet extends ThermostatSetbackCC { // Warning: (ae-missing-release-tag) "ThermostatSetbackCCReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class ThermostatSetbackCCReport extends ThermostatSetbackCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); - // (undocumented) - readonly setbackState: SetbackState; +export class ThermostatSetbackCCReport extends ThermostatSetbackCC { + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetbackCCReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; + readonly setbackState: SetbackState; + // (undocumented) + readonly setbackType: SetbackType; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; +} + +// Warning: (ae-missing-release-tag) "ThermostatSetbackCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ThermostatSetbackCCReportOptions { // (undocumented) - readonly setbackType: SetbackType; + setbackState: SetbackState; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + setbackType: SetbackType; } // Warning: (ae-missing-release-tag) "ThermostatSetbackCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ThermostatSetbackCCSet extends ThermostatSetbackCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetbackCCSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetbackCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; setbackState: SetbackState; // (undocumented) setbackType: SetbackType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatSetbackCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetbackCCSetOptions extends CCCommandOptions { +export interface ThermostatSetbackCCSetOptions { // (undocumented) setbackState: SetbackState; // (undocumented) setbackType: SetbackType; } -// Warning: (ae-missing-release-tag) "ThermostatSetbackCCValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const ThermostatSetbackCCValues: Readonly<{ - setbackState: { - readonly id: { - commandClass: (typeof CommandClasses)["Thermostat Setback"]; - property: "setbackState"; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses)["Thermostat Setback"]; - readonly endpoint: number; - readonly property: "setbackState"; - }; - readonly is: (valueId: ValueID_2) => boolean; - readonly meta: { - readonly min: -12.8; - readonly max: 12; - readonly label: "Setback state"; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; - readonly options: { - readonly internal: false; - readonly minVersion: 1; - readonly secret: false; - readonly stateful: true; - readonly supportsEndpoints: true; - readonly autoCreate: true; - }; - }; - setbackType: { - readonly id: { - commandClass: (typeof CommandClasses)["Thermostat Setback"]; - property: "setbackType"; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses)["Thermostat Setback"]; - readonly endpoint: number; - readonly property: "setbackType"; - }; - readonly is: (valueId: ValueID_2) => boolean; - readonly meta: { - readonly label: "Setback type"; - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; - readonly options: { - readonly internal: false; - readonly minVersion: 1; - readonly secret: false; - readonly stateful: true; - readonly supportsEndpoints: true; - readonly autoCreate: true; - }; - }; -}>; - // Warning: (ae-missing-release-tag) "ThermostatSetbackCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -17729,30 +18953,32 @@ export class ThermostatSetpointCC extends CommandClass { // (undocumented) ccCommand: ThermostatSetpointCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; + refreshValues(ctx: RefreshValuesContext): Promise; // (undocumented) - translatePropertyKey(applHost: ZWaveApplicationHost_2, property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB_2, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "ThermostatSetpointCCCapabilitiesGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCCapabilitiesGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCCapabilitiesGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: ThermostatSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatSetpointCCCapabilitiesGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCCapabilitiesGetOptions extends CCCommandOptions { +export interface ThermostatSetpointCCCapabilitiesGetOptions { // (undocumented) setpointType: ThermostatSetpointType; } @@ -17761,7 +18987,9 @@ export interface ThermostatSetpointCCCapabilitiesGetOptions extends CCCommandOpt // // @public (undocumented) export class ThermostatSetpointCCCapabilitiesReport extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCCapabilitiesReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCCapabilitiesReport; // (undocumented) maxValue: number; // (undocumented) @@ -17771,11 +18999,11 @@ export class ThermostatSetpointCCCapabilitiesReport extends ThermostatSetpointCC // (undocumented) minValueScale: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: ThermostatSetpointType; } @@ -17783,7 +19011,7 @@ export class ThermostatSetpointCCCapabilitiesReport extends ThermostatSetpointCC // Warning: (ae-missing-release-tag) "ThermostatSetpointCCCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCCapabilitiesReportOptions extends CCCommandOptions { +export interface ThermostatSetpointCCCapabilitiesReportOptions { // (undocumented) maxValue: number; // (undocumented) @@ -17800,19 +19028,21 @@ export interface ThermostatSetpointCCCapabilitiesReportOptions extends CCCommand // // @public (undocumented) export class ThermostatSetpointCCGet extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCGetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCGet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: ThermostatSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatSetpointCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCGetOptions extends CCCommandOptions { +export interface ThermostatSetpointCCGetOptions { // (undocumented) setpointType: ThermostatSetpointType; } @@ -17821,15 +19051,17 @@ export interface ThermostatSetpointCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class ThermostatSetpointCCReport extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) scale: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) type: ThermostatSetpointType; // (undocumented) @@ -17839,7 +19071,7 @@ export class ThermostatSetpointCCReport extends ThermostatSetpointCC { // Warning: (ae-missing-release-tag) "ThermostatSetpointCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCReportOptions extends CCCommandOptions { +export interface ThermostatSetpointCCReportOptions { // (undocumented) scale: number; // (undocumented) @@ -17852,15 +19084,17 @@ export interface ThermostatSetpointCCReportOptions extends CCCommandOptions { // // @public (undocumented) export class ThermostatSetpointCCSet extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCSet; // (undocumented) scale: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) setpointType: ThermostatSetpointType; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) value: number; } @@ -17868,7 +19102,7 @@ export class ThermostatSetpointCCSet extends ThermostatSetpointCC { // Warning: (ae-missing-release-tag) "ThermostatSetpointCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCSetOptions extends CCCommandOptions { +export interface ThermostatSetpointCCSetOptions { // (undocumented) scale: number; // (undocumented) @@ -17887,19 +19121,21 @@ export class ThermostatSetpointCCSupportedGet extends ThermostatSetpointCC { // // @public (undocumented) export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | ThermostatSetpointCCSupportedReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ThermostatSetpointCCSupportedReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedSetpointTypes: readonly ThermostatSetpointType[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "ThermostatSetpointCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ThermostatSetpointCCSupportedReportOptions extends CCCommandOptions { +export interface ThermostatSetpointCCSupportedReportOptions { // (undocumented) supportedSetpointTypes: ThermostatSetpointType[]; } @@ -17909,11 +19145,6 @@ export interface ThermostatSetpointCCSupportedReportOptions extends CCCommandOpt // @public (undocumented) export const ThermostatSetpointCCValues: Readonly<{ setpointScale: ((setpointType: ThermostatSetpointType) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Thermostat Setpoint"]; property: "setpointScale"; @@ -17925,27 +19156,23 @@ export const ThermostatSetpointCCValues: Readonly<{ readonly property: "setpointScale"; readonly propertyKey: ThermostatSetpointType; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; setpoint: ((setpointType: ThermostatSetpointType) => { - readonly meta: { - readonly label: `Setpoint (${string})`; - readonly ccSpecific: { - readonly setpointType: ThermostatSetpointType; - }; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["Thermostat Setpoint"]; property: "setpoint"; @@ -17957,6 +19184,15 @@ export const ThermostatSetpointCCValues: Readonly<{ readonly property: "setpoint"; readonly propertyKey: ThermostatSetpointType; }; + readonly meta: { + readonly label: `Setpoint (${string})`; + readonly ccSpecific: { + readonly setpointType: ThermostatSetpointType; + }; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -17968,31 +19204,6 @@ export const ThermostatSetpointCCValues: Readonly<{ readonly autoCreate: true; }; }; - setpointTypesInterpretation: { - readonly id: { - commandClass: (typeof CommandClasses)["Thermostat Setpoint"]; - property: "setpointTypesInterpretation"; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses)["Thermostat Setpoint"]; - readonly endpoint: number; - readonly property: "setpointTypesInterpretation"; - }; - readonly is: (valueId: ValueID_2) => boolean; - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; - readonly options: { - readonly stateful: true; - readonly secret: false; - readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; - readonly internal: true; - }; - }; supportedSetpointTypes: { readonly id: { commandClass: (typeof CommandClasses)["Thermostat Setpoint"]; @@ -18010,11 +19221,11 @@ export const ThermostatSetpointCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -18116,7 +19327,7 @@ export class TimeCC extends CommandClass { // (undocumented) ccCommand: TimeCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "TimeCCDateGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -18129,15 +19340,17 @@ export class TimeCCDateGet extends TimeCC { // // @public (undocumented) export class TimeCCDateReport extends TimeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TimeCCDateReportOptions); + constructor(options: WithAddress_2); // (undocumented) day: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TimeCCDateReport; + // (undocumented) month: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; // (undocumented) year: number; } @@ -18145,7 +19358,7 @@ export class TimeCCDateReport extends TimeCC { // Warning: (ae-missing-release-tag) "TimeCCDateReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TimeCCDateReportOptions extends CCCommandOptions { +export interface TimeCCDateReportOptions { // (undocumented) day: number; // (undocumented) @@ -18170,7 +19383,7 @@ export class TimeCCTimeOffsetGet extends TimeCC { // // @public (undocumented) export class TimeCCTimeOffsetReport extends TimeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TimeCCTimeOffsetReportOptions); + constructor(options: WithAddress_2); // (undocumented) dstEndDate: Date; // (undocumented) @@ -18178,17 +19391,19 @@ export class TimeCCTimeOffsetReport extends TimeCC { // (undocumented) dstStartDate: Date; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): TimeCCTimeOffsetReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) standardOffset: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "TimeCCTimeOffsetReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TimeCCTimeOffsetReportOptions extends CCCommandOptions { +export interface TimeCCTimeOffsetReportOptions { // (undocumented) dstEnd: Date; // (undocumented) @@ -18203,7 +19418,7 @@ export interface TimeCCTimeOffsetReportOptions extends CCCommandOptions { // // @public (undocumented) export class TimeCCTimeOffsetSet extends TimeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TimeCCTimeOffsetSetOptions); + constructor(options: WithAddress_2); // (undocumented) dstEndDate: Date; // (undocumented) @@ -18211,17 +19426,19 @@ export class TimeCCTimeOffsetSet extends TimeCC { // (undocumented) dstStartDate: Date; // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): TimeCCTimeOffsetSet; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) standardOffset: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "TimeCCTimeOffsetSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TimeCCTimeOffsetSetOptions extends CCCommandOptions { +export interface TimeCCTimeOffsetSetOptions { // (undocumented) dstEnd: Date; // (undocumented) @@ -18236,7 +19453,9 @@ export interface TimeCCTimeOffsetSetOptions extends CCCommandOptions { // // @public (undocumented) export class TimeCCTimeReport extends TimeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TimeCCTimeReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TimeCCTimeReport; // (undocumented) hour: number; // (undocumented) @@ -18244,15 +19463,15 @@ export class TimeCCTimeReport extends TimeCC { // (undocumented) second: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "TimeCCTimeReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TimeCCTimeReportOptions extends CCCommandOptions { +export interface TimeCCTimeReportOptions { // (undocumented) hour: number; // (undocumented) @@ -18288,7 +19507,7 @@ export class TimeParametersCC extends CommandClass { // (undocumented) ccCommand: TimeParametersCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "TimeParametersCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -18301,34 +19520,46 @@ export class TimeParametersCCGet extends TimeParametersCC { // // @public (undocumented) export class TimeParametersCCReport extends TimeParametersCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress_2); // (undocumented) - get dateAndTime(): Date; + dateAndTime: Date; + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TimeParametersCCReport; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; +} + +// Warning: (ae-missing-release-tag) "TimeParametersCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface TimeParametersCCReportOptions { // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + dateAndTime: Date; } // Warning: (ae-missing-release-tag) "TimeParametersCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class TimeParametersCCSet extends TimeParametersCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TimeParametersCCSetOptions); + constructor(options: WithAddress_2); // (undocumented) dateAndTime: Date; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): TimeParametersCCSet; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "TimeParametersCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TimeParametersCCSetOptions extends CCCommandOptions { +export interface TimeParametersCCSetOptions { // (undocumented) dateAndTime: Date; // (undocumented) @@ -18406,15 +19637,6 @@ export class TransportServiceCC extends CommandClass implements SinglecastCC_2); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) @@ -18432,23 +19654,25 @@ export class TransportServiceCCFirstSegment extends TransportServiceCC { // (undocumented) expectMoreMessages(): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TransportServiceCCFirstSegment; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) headerExtension: Buffer | undefined; // (undocumented) partialDatagram: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) sessionId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "TransportServiceCCFirstSegmentOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TransportServiceCCFirstSegmentOptions extends CCCommandOptions { +export interface TransportServiceCCFirstSegmentOptions { // (undocumented) datagramSize: number; // (undocumented) @@ -18463,19 +19687,21 @@ export interface TransportServiceCCFirstSegmentOptions extends CCCommandOptions // // @public (undocumented) export class TransportServiceCCSegmentComplete extends TransportServiceCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TransportServiceCCSegmentCompleteOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TransportServiceCCSegmentComplete; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) sessionId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "TransportServiceCCSegmentCompleteOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TransportServiceCCSegmentCompleteOptions extends CCCommandOptions { +export interface TransportServiceCCSegmentCompleteOptions { // (undocumented) sessionId: number; } @@ -18484,21 +19710,23 @@ export interface TransportServiceCCSegmentCompleteOptions extends CCCommandOptio // // @public (undocumented) export class TransportServiceCCSegmentRequest extends TransportServiceCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TransportServiceCCSegmentRequestOptions); + constructor(options: WithAddress); // (undocumented) datagramOffset: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): TransportServiceCCSegmentRequest; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) sessionId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "TransportServiceCCSegmentRequestOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TransportServiceCCSegmentRequestOptions extends CCCommandOptions { +export interface TransportServiceCCSegmentRequestOptions { // (undocumented) datagramOffset: number; // (undocumented) @@ -18509,19 +19737,21 @@ export interface TransportServiceCCSegmentRequestOptions extends CCCommandOption // // @public (undocumented) export class TransportServiceCCSegmentWait extends TransportServiceCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TransportServiceCCSegmentWaitOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TransportServiceCCSegmentWait; // (undocumented) pendingSegments: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "TransportServiceCCSegmentWaitOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface TransportServiceCCSegmentWaitOptions extends CCCommandOptions { +export interface TransportServiceCCSegmentWaitOptions { // (undocumented) pendingSegments: number; } @@ -18530,7 +19760,7 @@ export interface TransportServiceCCSegmentWaitOptions extends CCCommandOptions { // // @public (undocumented) export class TransportServiceCCSubsequentSegment extends TransportServiceCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | TransportServiceCCSubsequentSegmentOptions); + constructor(options: WithAddress); // (undocumented) protected computeEncapsulationOverhead(): number; // (undocumented) @@ -18545,22 +19775,24 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { ...TransportServiceCCSubsequentSegment[] ]): boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): TransportServiceCCSubsequentSegment; + // (undocumented) getPartialCCSessionId(): Record | undefined; // (undocumented) headerExtension: Buffer | undefined; // (undocumented) - mergePartialCCs(applHost: ZWaveApplicationHost_2, partials: [ + mergePartialCCs(partials: [ TransportServiceCCFirstSegment, ...TransportServiceCCSubsequentSegment[] - ]): void; + ], ctx: CCParsingContext_2): void; // (undocumented) partialDatagram: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) sessionId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "TransportServiceCCSubsequentSegmentOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -18606,19 +19838,19 @@ export const TransportServiceTimeouts: { export class UserCodeCC extends CommandClass { // (undocumented) ccCommand: UserCodeCommand; - static getSupportedASCIICharsCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; - static getSupportedKeypadModesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; - static getSupportedUserIDStatusesCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; - static getSupportedUsersCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): MaybeNotKnown; - static getUserCodeCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, userId: number): MaybeNotKnown; - static getUserIdStatusCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2, userId: number): MaybeNotKnown; + static getSupportedASCIICharsCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getSupportedKeypadModesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getSupportedUserIDStatusesCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getSupportedUsersCached(ctx: GetValueDB_2, endpoint: EndpointId_2): MaybeNotKnown; + static getUserCodeCached(ctx: GetValueDB_2, endpoint: EndpointId_2, userId: number): MaybeNotKnown; + static getUserIdStatusCached(ctx: GetValueDB_2, endpoint: EndpointId_2, userId: number): MaybeNotKnown; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - refreshValues(applHost: ZWaveApplicationHost_2): Promise; - static supportsAdminCodeCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): boolean; - static supportsAdminCodeDeactivationCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): boolean; - static supportsMultipleUserCodeSetCached(applHost: ZWaveApplicationHost_2, endpoint: IZWaveEndpoint_2): boolean; + refreshValues(ctx: RefreshValuesContext): Promise; + static supportsAdminCodeCached(ctx: GetValueDB_2, endpoint: EndpointId_2): boolean; + static supportsAdminCodeDeactivationCached(ctx: GetValueDB_2, endpoint: EndpointId_2): boolean; + static supportsMultipleUserCodeSetCached(ctx: GetValueDB_2, endpoint: EndpointId_2): boolean; } // Warning: (ae-missing-release-tag) "UserCodeCCAdminCodeGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -18631,19 +19863,21 @@ export class UserCodeCCAdminCodeGet extends UserCodeCC { // // @public (undocumented) export class UserCodeCCAdminCodeReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCAdminCodeReportOptions); + constructor(options: WithAddress); // (undocumented) readonly adminCode: string; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCAdminCodeReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCAdminCodeReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCAdminCodeReportOptions extends CCCommandOptions { +export interface UserCodeCCAdminCodeReportOptions { // (undocumented) adminCode: string; } @@ -18652,19 +19886,21 @@ export interface UserCodeCCAdminCodeReportOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCAdminCodeSet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCAdminCodeSetOptions); + constructor(options: WithAddress); // (undocumented) adminCode: string; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCAdminCodeSet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCAdminCodeSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCAdminCodeSetOptions extends CCCommandOptions { +export interface UserCodeCCAdminCodeSetOptions { // (undocumented) adminCode: string; } @@ -18679,9 +19915,11 @@ export class UserCodeCCCapabilitiesGet extends UserCodeCC { // // @public (undocumented) export class UserCodeCCCapabilitiesReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCCapabilitiesReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCCapabilitiesReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedASCIIChars: string; // (undocumented) @@ -18699,13 +19937,13 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { // (undocumented) readonly supportsUserCodeChecksum: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCCapabilitiesReportOptions extends CCCommandOptions { +export interface UserCodeCCCapabilitiesReportOptions { // (undocumented) supportedASCIIChars: string; // (undocumented) @@ -18728,13 +19966,15 @@ export interface UserCodeCCCapabilitiesReportOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCExtendedUserCodeGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(_raw: CCRaw, _ctx: CCParsingContext_2): UserCodeCCExtendedUserCodeGet; // (undocumented) reportMore: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) userId: number; } @@ -18742,7 +19982,7 @@ export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { // Warning: (ae-missing-release-tag) "UserCodeCCExtendedUserCodeGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCExtendedUserCodeGetOptions extends CCCommandOptions { +export interface UserCodeCCExtendedUserCodeGetOptions { // (undocumented) reportMore?: boolean; // (undocumented) @@ -18753,28 +19993,42 @@ export interface UserCodeCCExtendedUserCodeGetOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCExtendedUserCodeReport; // (undocumented) readonly nextUserId: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // Warning: (ae-forgotten-export) The symbol "UserCode" needs to be exported by the entry point index.d.ts // // (undocumented) readonly userCodes: readonly UserCode[]; } +// Warning: (ae-missing-release-tag) "UserCodeCCExtendedUserCodeReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface UserCodeCCExtendedUserCodeReportOptions { + // (undocumented) + nextUserId: number; + // (undocumented) + userCodes: UserCode[]; +} + // Warning: (ae-missing-release-tag) "UserCodeCCExtendedUserCodeSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCExtendedUserCodeSetOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(_raw: CCRaw, _ctx: CCParsingContext_2): UserCodeCCExtendedUserCodeSet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) userCodes: UserCodeCCSetOptions[]; } @@ -18782,7 +20036,7 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { // Warning: (ae-missing-release-tag) "UserCodeCCExtendedUserCodeSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCExtendedUserCodeSetOptions extends CCCommandOptions { +export interface UserCodeCCExtendedUserCodeSetOptions { // (undocumented) userCodes: UserCodeCCSetOptions[]; } @@ -18791,11 +20045,13 @@ export interface UserCodeCCExtendedUserCodeSetOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCGet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCGet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) userId: number; } @@ -18803,7 +20059,7 @@ export class UserCodeCCGet extends UserCodeCC { // Warning: (ae-missing-release-tag) "UserCodeCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCGetOptions extends CCCommandOptions { +export interface UserCodeCCGetOptions { // (undocumented) userId: number; } @@ -18818,21 +20074,23 @@ export class UserCodeCCKeypadModeGet extends UserCodeCC { // // @public (undocumented) export class UserCodeCCKeypadModeReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCKeypadModeReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCKeypadModeReport; // (undocumented) readonly keypadMode: KeypadMode; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCKeypadModeReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCKeypadModeReportOptions extends CCCommandOptions { +export interface UserCodeCCKeypadModeReportOptions { // (undocumented) keypadMode: KeypadMode; } @@ -18841,19 +20099,21 @@ export interface UserCodeCCKeypadModeReportOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCKeypadModeSet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCKeypadModeSetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCKeypadModeSet; // (undocumented) keypadMode: KeypadMode; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCKeypadModeSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCKeypadModeSetOptions extends CCCommandOptions { +export interface UserCodeCCKeypadModeSetOptions { // (undocumented) keypadMode: KeypadMode; } @@ -18863,13 +20123,15 @@ export interface UserCodeCCKeypadModeSetOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCReport extends UserCodeCC implements NotificationEventPayload { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCReportOptions); + constructor(options: WithAddress); // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCReport; // (undocumented) - serialize(): Buffer; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) toNotificationEventParameters(): { userId: number; @@ -18885,7 +20147,7 @@ export class UserCodeCCReport extends UserCodeCC implements NotificationEventPay // Warning: (ae-missing-release-tag) "UserCodeCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCReportOptions extends CCCommandOptions { +export interface UserCodeCCReportOptions { // (undocumented) userCode?: string | Buffer; // (undocumented) @@ -18898,11 +20160,13 @@ export interface UserCodeCCReportOptions extends CCCommandOptions { // // @public (undocumented) export class UserCodeCCSet extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & UserCodeCCSetOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCSet; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) userCode: string | Buffer; // (undocumented) @@ -18938,11 +20202,13 @@ export class UserCodeCCUserCodeChecksumGet extends UserCodeCC { // // @public (undocumented) export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCUserCodeChecksumReportOptions); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCUserCodeChecksumReport; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly userCodeChecksum: number; } @@ -18950,7 +20216,7 @@ export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { // Warning: (ae-missing-release-tag) "UserCodeCCUserCodeChecksumReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCUserCodeChecksumReportOptions extends CCCommandOptions { +export interface UserCodeCCUserCodeChecksumReportOptions { // (undocumented) userCodeChecksum: number; } @@ -18965,19 +20231,21 @@ export class UserCodeCCUsersNumberGet extends UserCodeCC { // // @public (undocumented) export class UserCodeCCUsersNumberReport extends UserCodeCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | UserCodeCCUsersNumberReportOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): UserCodeCCUsersNumberReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) readonly supportedUsers: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "UserCodeCCUsersNumberReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface UserCodeCCUsersNumberReportOptions extends CCCommandOptions { +export interface UserCodeCCUsersNumberReportOptions { // (undocumented) supportedUsers: number; } @@ -18987,11 +20255,6 @@ export interface UserCodeCCUsersNumberReportOptions extends CCCommandOptions { // @public (undocumented) export const UserCodeCCValues: Readonly<{ userCode: ((userId: number) => { - readonly meta: { - readonly type: "any"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["User Code"]; property: "userCode"; @@ -19003,24 +20266,23 @@ export const UserCodeCCValues: Readonly<{ readonly property: "userCode"; readonly propertyKey: number; }; + readonly meta: { + readonly type: "any"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { - readonly stateful: true; readonly internal: false; - readonly minVersion: 1; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly minVersion: 1; readonly secret: true; }; }; userIdStatus: ((userId: number) => { - readonly meta: { - readonly label: `User ID status (${number})`; - readonly type: "number"; - readonly readable: true; - readonly writeable: true; - }; readonly id: { commandClass: (typeof CommandClasses)["User Code"]; property: "userIdStatus"; @@ -19032,6 +20294,12 @@ export const UserCodeCCValues: Readonly<{ readonly property: "userIdStatus"; readonly propertyKey: number; }; + readonly meta: { + readonly label: `User ID status (${number})`; + readonly type: "number"; + readonly readable: true; + readonly writeable: true; + }; }) & { is: (valueId: ValueID_2) => boolean; readonly options: { @@ -19063,10 +20331,10 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; readonly minVersion: 2; readonly secret: true; }; @@ -19089,11 +20357,11 @@ export const UserCodeCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; }; }; @@ -19114,11 +20382,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19139,11 +20407,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19164,11 +20432,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19189,11 +20457,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19214,11 +20482,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19239,11 +20507,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19264,11 +20532,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19289,11 +20557,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19314,11 +20582,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19339,11 +20607,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19364,11 +20632,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19389,11 +20657,11 @@ export const UserCodeCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -19521,7 +20789,7 @@ export class VersionCC extends CommandClass { // (undocumented) determineRequiredCCInterviews(): readonly CommandClasses[]; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "VersionCCCapabilitiesGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -19534,13 +20802,15 @@ export class VersionCCCapabilitiesGet extends VersionCC { // // @public (undocumented) export class VersionCCCapabilitiesReport extends VersionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (VersionCCCapabilitiesReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): VersionCCCapabilitiesReport; + // (undocumented) + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) supportsZWaveSoftwareGet: boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "VersionCCCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -19555,19 +20825,21 @@ export interface VersionCCCapabilitiesReportOptions { // // @public (undocumented) export class VersionCCCommandClassGet extends VersionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | VersionCCCommandClassGetOptions); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): VersionCCCommandClassGet; // (undocumented) requestedCC: CommandClasses; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "VersionCCCommandClassGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface VersionCCCommandClassGetOptions extends CCCommandOptions { +export interface VersionCCCommandClassGetOptions { // (undocumented) requestedCC: CommandClasses; } @@ -19576,21 +20848,23 @@ export interface VersionCCCommandClassGetOptions extends CCCommandOptions { // // @public (undocumented) export class VersionCCCommandClassReport extends VersionCC { - constructor(host: ZWaveHost_2, options: VersionCCCommandClassReportOptions | CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) ccVersion: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): VersionCCCommandClassReport; + // (undocumented) requestedCC: CommandClasses; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "VersionCCCommandClassReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface VersionCCCommandClassReportOptions extends CCCommandOptions { +export interface VersionCCCommandClassReportOptions { // (undocumented) ccVersion: number; // (undocumented) @@ -19607,19 +20881,21 @@ export class VersionCCGet extends VersionCC { // // @public (undocumented) export class VersionCCReport extends VersionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (VersionCCReportOptions & CCCommandOptions)); + constructor(options: WithAddress); // (undocumented) readonly firmwareVersions: string[]; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): VersionCCReport; + // (undocumented) readonly hardwareVersion: number | undefined; // (undocumented) readonly libraryType: ZWaveLibraryTypes; // (undocumented) readonly protocolVersion: string; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; } // Warning: (ae-missing-release-tag) "VersionCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -19658,10 +20934,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19684,10 +20960,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19710,10 +20986,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19736,10 +21012,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19762,10 +21038,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19788,10 +21064,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19814,10 +21090,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19840,10 +21116,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19866,10 +21142,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly supportsEndpoints: false; }; @@ -19891,10 +21167,10 @@ export const VersionCCValues: Readonly<{ readonly writeable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly supportsEndpoints: true; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 3; readonly internal: true; }; @@ -19917,10 +21193,10 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { - readonly stateful: true; - readonly secret: false; readonly internal: false; readonly autoCreate: true; + readonly stateful: true; + readonly secret: false; readonly minVersion: 2; readonly supportsEndpoints: false; }; @@ -19943,11 +21219,11 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -19972,11 +21248,11 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -19998,11 +21274,11 @@ export const VersionCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -20018,7 +21294,7 @@ export class VersionCCZWaveSoftwareGet extends VersionCC { // // @public (undocumented) export class VersionCCZWaveSoftwareReport extends VersionCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly applicationBuildNumber: number; // (undocumented) @@ -20028,19 +21304,45 @@ export class VersionCCZWaveSoftwareReport extends VersionCC { // (undocumented) readonly applicationVersion: string; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): VersionCCZWaveSoftwareReport; + // (undocumented) readonly hostInterfaceBuildNumber: number; // (undocumented) readonly hostInterfaceVersion: string; // (undocumented) readonly sdkVersion: string; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly zWaveProtocolBuildNumber: number; // (undocumented) readonly zWaveProtocolVersion: string; } +// Warning: (ae-missing-release-tag) "VersionCCZWaveSoftwareReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface VersionCCZWaveSoftwareReportOptions { + // (undocumented) + applicationBuildNumber: number; + // (undocumented) + applicationFrameworkAPIVersion: string; + // (undocumented) + applicationFrameworkBuildNumber: number; + // (undocumented) + applicationVersion: string; + // (undocumented) + hostInterfaceBuildNumber: number; + // (undocumented) + hostInterfaceVersion: string; + // (undocumented) + sdkVersion: string; + // (undocumented) + zWaveProtocolBuildNumber: number; + // (undocumented) + zWaveProtocolVersion: string; +} + // Warning: (ae-missing-release-tag) "VersionCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -20063,6 +21365,11 @@ export enum VersionCommand { ZWaveSoftwareReport = 24 } +// Warning: (ae-missing-release-tag) "VirtualCCAPIEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type VirtualCCAPIEndpoint = CCAPIEndpoint & VirtualEndpointId; + // Warning: (ae-missing-release-tag) "WakeUpCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -20070,7 +21377,7 @@ export class WakeUpCC extends CommandClass { // (undocumented) ccCommand: WakeUpCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "WakeUpCCIntervalCapabilitiesGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -20083,23 +21390,41 @@ export class WakeUpCCIntervalCapabilitiesGet extends WakeUpCC { // // @public (undocumented) export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly defaultWakeUpInterval: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): WakeUpCCIntervalCapabilitiesReport; + // (undocumented) readonly maxWakeUpInterval: number; // (undocumented) readonly minWakeUpInterval: number; // (undocumented) - persistValues(applHost: ZWaveApplicationHost_2): boolean; + persistValues(ctx: PersistValuesContext): boolean; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly wakeUpIntervalSteps: number; // (undocumented) readonly wakeUpOnDemandSupported: boolean; } +// Warning: (ae-missing-release-tag) "WakeUpCCIntervalCapabilitiesReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WakeUpCCIntervalCapabilitiesReportOptions { + // (undocumented) + defaultWakeUpInterval: number; + // (undocumented) + maxWakeUpInterval: number; + // (undocumented) + minWakeUpInterval: number; + // (undocumented) + wakeUpIntervalSteps: number; + // (undocumented) + wakeUpOnDemandSupported: boolean; +} + // Warning: (ae-missing-release-tag) "WakeUpCCIntervalGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -20110,26 +21435,40 @@ export class WakeUpCCIntervalGet extends WakeUpCC { // // @public (undocumented) export class WakeUpCCIntervalReport extends WakeUpCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions); + constructor(options: WithAddress); // (undocumented) readonly controllerNodeId: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + static from(raw: CCRaw, ctx: CCParsingContext_2): WakeUpCCIntervalReport; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) readonly wakeUpInterval: number; } +// Warning: (ae-missing-release-tag) "WakeUpCCIntervalReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WakeUpCCIntervalReportOptions { + // (undocumented) + controllerNodeId: number; + // (undocumented) + wakeUpInterval: number; +} + // Warning: (ae-missing-release-tag) "WakeUpCCIntervalSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class WakeUpCCIntervalSet extends WakeUpCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | WakeUpCCIntervalSetOptions); + constructor(options: WithAddress); // (undocumented) controllerNodeId: number; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext_2): WakeUpCCIntervalSet; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + serialize(ctx: CCEncodingContext_2): Buffer; + // (undocumented) + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) wakeUpInterval: number; } @@ -20137,7 +21476,7 @@ export class WakeUpCCIntervalSet extends WakeUpCC { // Warning: (ae-missing-release-tag) "WakeUpCCIntervalSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WakeUpCCIntervalSetOptions extends CCCommandOptions { +export interface WakeUpCCIntervalSetOptions { // (undocumented) controllerNodeId: number; // (undocumented) @@ -20171,9 +21510,9 @@ export const WakeUpCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly autoCreate: true; readonly internal: true; readonly supportsEndpoints: false; readonly minVersion: number; @@ -20199,11 +21538,11 @@ export const WakeUpCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -20225,11 +21564,11 @@ export const WakeUpCCValues: Readonly<{ readonly readable: true; }; readonly options: { + readonly internal: false; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; - readonly internal: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; }; }; @@ -20307,28 +21646,30 @@ export class WindowCoveringCC extends CommandClass { // (undocumented) ccCommand: WindowCoveringCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost): Promise; + interview(ctx: InterviewContext): Promise; // (undocumented) - translatePropertyKey(_applHost: ZWaveApplicationHost, _property: string | number, propertyKey: string | number): string | undefined; + translatePropertyKey(ctx: GetValueDB, property: string | number, propertyKey: string | number): string | undefined; } // Warning: (ae-missing-release-tag) "WindowCoveringCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class WindowCoveringCCGet extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | WindowCoveringCCGetOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCGet; // (undocumented) parameter: WindowCoveringParameter; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "WindowCoveringCCGetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WindowCoveringCCGetOptions extends CCCommandOptions { +export interface WindowCoveringCCGetOptions { // (undocumented) parameter: WindowCoveringParameter; } @@ -20337,41 +21678,59 @@ export interface WindowCoveringCCGetOptions extends CCCommandOptions { // // @public (undocumented) export class WindowCoveringCCReport extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions); + constructor(options: WithAddress_2); // (undocumented) readonly currentValue: number; // (undocumented) readonly duration: Duration_2; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCReport; + // (undocumented) readonly parameter: WindowCoveringParameter; // (undocumented) readonly targetValue: number; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; +} + +// Warning: (ae-missing-release-tag) "WindowCoveringCCReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WindowCoveringCCReportOptions { + // (undocumented) + currentValue: number; + // (undocumented) + duration: Duration_2; + // (undocumented) + parameter: WindowCoveringParameter; + // (undocumented) + targetValue: number; } // Warning: (ae-missing-release-tag) "WindowCoveringCCSet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class WindowCoveringCCSet extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | WindowCoveringCCSetOptions); + constructor(options: WithAddress_2); // (undocumented) duration: Duration_2 | undefined; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCSet; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) targetValues: { parameter: WindowCoveringParameter; value: number; }[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "WindowCoveringCCSetOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WindowCoveringCCSetOptions extends CCCommandOptions { +export interface WindowCoveringCCSetOptions { // (undocumented) duration?: Duration_2 | string; // (undocumented) @@ -20385,23 +21744,25 @@ export interface WindowCoveringCCSetOptions extends CCCommandOptions { // // @public (undocumented) export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | WindowCoveringCCStartLevelChangeOptions); + constructor(options: WithAddress_2); // (undocumented) direction: keyof typeof LevelChangeDirection; // (undocumented) duration: Duration_2 | undefined; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCStartLevelChange; + // (undocumented) parameter: WindowCoveringParameter; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "WindowCoveringCCStartLevelChangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WindowCoveringCCStartLevelChangeOptions extends CCCommandOptions { +export interface WindowCoveringCCStartLevelChangeOptions { // (undocumented) direction: keyof typeof LevelChangeDirection; // (undocumented) @@ -20414,19 +21775,21 @@ export interface WindowCoveringCCStartLevelChangeOptions extends CCCommandOption // // @public (undocumented) export class WindowCoveringCCStopLevelChange extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | WindowCoveringCCStopLevelChangeOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCStopLevelChange; // (undocumented) parameter: WindowCoveringParameter; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "WindowCoveringCCStopLevelChangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WindowCoveringCCStopLevelChangeOptions extends CCCommandOptions { +export interface WindowCoveringCCStopLevelChangeOptions { // (undocumented) parameter: WindowCoveringParameter; } @@ -20441,19 +21804,21 @@ export class WindowCoveringCCSupportedGet extends WindowCoveringCC { // // @public (undocumented) export class WindowCoveringCCSupportedReport extends WindowCoveringCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | WindowCoveringCCSupportedReportOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCSupportedReport; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) readonly supportedParameters: readonly WindowCoveringParameter[]; // (undocumented) - toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry; + toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry; } // Warning: (ae-missing-release-tag) "WindowCoveringCCSupportedReportOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface WindowCoveringCCSupportedReportOptions extends CCCommandOptions { +export interface WindowCoveringCCSupportedReportOptions { // (undocumented) supportedParameters: readonly WindowCoveringParameter[]; } @@ -20463,6 +21828,17 @@ export interface WindowCoveringCCSupportedReportOptions extends CCCommandOptions // @public (undocumented) export const WindowCoveringCCValues: Readonly<{ levelChangeDown: ((parameter: WindowCoveringParameter) => { + readonly id: { + commandClass: (typeof CommandClasses_2)["Window Covering"]; + property: "levelChangeDown"; + propertyKey: WindowCoveringParameter; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; + readonly endpoint: number; + readonly property: "levelChangeDown"; + readonly propertyKey: WindowCoveringParameter; + }; readonly meta: { readonly label: `${string} - ${string}`; readonly valueChangeOptions: readonly ["transitionDuration"]; @@ -20477,17 +21853,6 @@ export const WindowCoveringCCValues: Readonly<{ readonly type: "boolean"; readonly writeable: true; }; - readonly id: { - commandClass: (typeof CommandClasses_2)["Window Covering"]; - property: "levelChangeDown"; - propertyKey: WindowCoveringParameter; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; - readonly endpoint: number; - readonly property: "levelChangeDown"; - readonly propertyKey: WindowCoveringParameter; - }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -20500,6 +21865,17 @@ export const WindowCoveringCCValues: Readonly<{ }; }; levelChangeUp: ((parameter: WindowCoveringParameter) => { + readonly id: { + commandClass: (typeof CommandClasses_2)["Window Covering"]; + property: "levelChangeUp"; + propertyKey: WindowCoveringParameter; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; + readonly endpoint: number; + readonly property: "levelChangeUp"; + readonly propertyKey: WindowCoveringParameter; + }; readonly meta: { readonly label: `${string} - ${string}`; readonly valueChangeOptions: readonly ["transitionDuration"]; @@ -20514,17 +21890,6 @@ export const WindowCoveringCCValues: Readonly<{ readonly type: "boolean"; readonly writeable: true; }; - readonly id: { - commandClass: (typeof CommandClasses_2)["Window Covering"]; - property: "levelChangeUp"; - propertyKey: WindowCoveringParameter; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; - readonly endpoint: number; - readonly property: "levelChangeUp"; - readonly propertyKey: WindowCoveringParameter; - }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -20537,15 +21902,6 @@ export const WindowCoveringCCValues: Readonly<{ }; }; duration: ((parameter: WindowCoveringParameter) => { - readonly meta: { - readonly label: `Remaining duration - ${string}`; - readonly ccSpecific: { - readonly parameter: WindowCoveringParameter; - }; - readonly writeable: false; - readonly type: "duration"; - readonly readable: true; - }; readonly id: { commandClass: (typeof CommandClasses_2)["Window Covering"]; property: "duration"; @@ -20557,6 +21913,15 @@ export const WindowCoveringCCValues: Readonly<{ readonly property: "duration"; readonly propertyKey: WindowCoveringParameter; }; + readonly meta: { + readonly label: `Remaining duration - ${string}`; + readonly ccSpecific: { + readonly parameter: WindowCoveringParameter; + }; + readonly writeable: false; + readonly type: "duration"; + readonly readable: true; + }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -20569,6 +21934,17 @@ export const WindowCoveringCCValues: Readonly<{ }; }; targetValue: ((parameter: WindowCoveringParameter) => { + readonly id: { + commandClass: (typeof CommandClasses_2)["Window Covering"]; + property: "targetValue"; + propertyKey: WindowCoveringParameter; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; + readonly endpoint: number; + readonly property: "targetValue"; + readonly propertyKey: WindowCoveringParameter; + }; readonly meta: { readonly label: `Target value - ${string}`; readonly writeable: boolean; @@ -20585,17 +21961,6 @@ export const WindowCoveringCCValues: Readonly<{ readonly type: "number"; readonly readable: true; }; - readonly id: { - commandClass: (typeof CommandClasses_2)["Window Covering"]; - property: "targetValue"; - propertyKey: WindowCoveringParameter; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; - readonly endpoint: number; - readonly property: "targetValue"; - readonly propertyKey: WindowCoveringParameter; - }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -20608,6 +21973,17 @@ export const WindowCoveringCCValues: Readonly<{ }; }; currentValue: ((parameter: WindowCoveringParameter) => { + readonly id: { + commandClass: (typeof CommandClasses_2)["Window Covering"]; + property: "currentValue"; + propertyKey: WindowCoveringParameter; + }; + readonly endpoint: (endpoint?: number | undefined) => { + readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; + readonly endpoint: number; + readonly property: "currentValue"; + readonly propertyKey: WindowCoveringParameter; + }; readonly meta: { readonly label: `Current value - ${string}`; readonly states: { @@ -20622,17 +21998,6 @@ export const WindowCoveringCCValues: Readonly<{ readonly type: "number"; readonly readable: true; }; - readonly id: { - commandClass: (typeof CommandClasses_2)["Window Covering"]; - property: "currentValue"; - propertyKey: WindowCoveringParameter; - }; - readonly endpoint: (endpoint?: number | undefined) => { - readonly commandClass: (typeof CommandClasses_2)["Window Covering"]; - readonly endpoint: number; - readonly property: "currentValue"; - readonly propertyKey: WindowCoveringParameter; - }; }) & { is: (valueId: ValueID) => boolean; readonly options: { @@ -20661,11 +22026,11 @@ export const WindowCoveringCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -20780,7 +22145,7 @@ export class ZWavePlusCC extends CommandClass { // (undocumented) ccCommand: ZWavePlusCommand; // (undocumented) - interview(applHost: ZWaveApplicationHost_2): Promise; + interview(ctx: InterviewContext): Promise; } // Warning: (ae-missing-release-tag) "ZWavePlusCCGet" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -20793,7 +22158,9 @@ export class ZWavePlusCCGet extends ZWavePlusCC { // // @public (undocumented) export class ZWavePlusCCReport extends ZWavePlusCC { - constructor(host: ZWaveHost_2, options: CommandClassDeserializationOptions | (CCCommandOptions & ZWavePlusCCReportOptions)); + constructor(options: WithAddress); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext_2): ZWavePlusCCReport; // (undocumented) installerIcon: number; // (undocumented) @@ -20801,9 +22168,9 @@ export class ZWavePlusCCReport extends ZWavePlusCC { // (undocumented) roleType: ZWavePlusRoleType; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext_2): Buffer; // (undocumented) - toLogEntry(host?: ZWaveValueHost_2): MessageOrCCLogEntry_2; + toLogEntry(ctx?: GetValueDB_2): MessageOrCCLogEntry_2; // (undocumented) userIcon: number; // (undocumented) @@ -20847,11 +22214,11 @@ export const ZWavePlusCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -20872,11 +22239,11 @@ export const ZWavePlusCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly supportsEndpoints: true; + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly supportsEndpoints: true; - readonly autoCreate: true; readonly internal: true; }; }; @@ -20897,10 +22264,10 @@ export const ZWavePlusCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; readonly internal: true; }; @@ -20922,10 +22289,10 @@ export const ZWavePlusCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; readonly internal: true; }; @@ -20947,10 +22314,10 @@ export const ZWavePlusCCValues: Readonly<{ readonly writeable: true; }; readonly options: { + readonly autoCreate: true; readonly stateful: true; readonly secret: false; readonly minVersion: 1; - readonly autoCreate: true; readonly supportsEndpoints: false; readonly internal: true; }; @@ -21013,17 +22380,19 @@ export class ZWaveProtocolCC extends CommandClass { // // @public (undocumented) export class ZWaveProtocolCCAcceptLost extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCAcceptLostOptions); + constructor(options: WithAddress_2); // (undocumented) accepted: boolean; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCAcceptLost; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCAcceptLostOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCAcceptLostOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAcceptLostOptions { // (undocumented) accepted: boolean; } @@ -21032,19 +22401,21 @@ export interface ZWaveProtocolCCAcceptLostOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCAssignIDs extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCAssignIDsOptions); + constructor(options: WithAddress_2); // (undocumented) assignedNodeId: number; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCAssignIDs; + // (undocumented) homeId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCAssignIDsOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCAssignIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAssignIDsOptions { // (undocumented) assignedNodeId: number; // (undocumented) @@ -21055,7 +22426,7 @@ export interface ZWaveProtocolCCAssignIDsOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCAssignReturnRouteOptions); + constructor(options: WithAddress_2); // (undocumented) destinationNodeId: number; // (undocumented) @@ -21063,17 +22434,19 @@ export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { // (undocumented) destinationWakeUp: WakeUpTime; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCAssignReturnRoute; + // (undocumented) repeaters: number[]; // (undocumented) routeIndex: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCAssignReturnRouteOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCAssignReturnRouteOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAssignReturnRouteOptions { // (undocumented) destinationNodeId: number; // (undocumented) @@ -21090,11 +22463,13 @@ export interface ZWaveProtocolCCAssignReturnRouteOptions extends CCCommandOption // // @public (undocumented) export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCAssignReturnRoutePriorityOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCAssignReturnRoutePriority; // (undocumented) routeNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) targetNodeId: number; } @@ -21102,7 +22477,7 @@ export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCAssignReturnRoutePriorityOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions { // (undocumented) routeNumber: number; // (undocumented) @@ -21131,17 +22506,19 @@ export class ZWaveProtocolCCAutomaticControllerUpdateStart extends ZWaveProtocol // // @public (undocumented) export class ZWaveProtocolCCCommandComplete extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCCommandCompleteOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCCommandComplete; // (undocumented) sequenceNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCCommandCompleteOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCCommandCompleteOptions extends CCCommandOptions { +export interface ZWaveProtocolCCCommandCompleteOptions { // (undocumented) sequenceNumber: number; } @@ -21156,13 +22533,15 @@ export class ZWaveProtocolCCExcludeRequest extends ZWaveProtocolCCNodeInformatio // // @public (undocumented) export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCFindNodesInRangeOptions); + constructor(options: WithAddress_2); // (undocumented) candidateNodeIds: number[]; // (undocumented) dataRate: ZWaveDataRate; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCFindNodesInRange; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) wakeUpTime: WakeUpTime; } @@ -21170,7 +22549,7 @@ export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCFindNodesInRangeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCFindNodesInRangeOptions extends CCCommandOptions { +export interface ZWaveProtocolCCFindNodesInRangeOptions { // (undocumented) candidateNodeIds: number[]; // (undocumented) @@ -21189,17 +22568,19 @@ export class ZWaveProtocolCCGetNodesInRange extends ZWaveProtocolCC { // // @public (undocumented) export class ZWaveProtocolCCLost extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCLostOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCLost; // (undocumented) lostNodeId: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCLostOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCLostOptions extends CCCommandOptions { +export interface ZWaveProtocolCCLostOptions { // (undocumented) lostNodeId: number; } @@ -21208,10 +22589,12 @@ export interface ZWaveProtocolCCLostOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC implements NodeInformationFrame { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNewNodeRegisteredOptions); + constructor(options: WithAddress_2); // (undocumented) basicDeviceClass: BasicDeviceClass; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNewNodeRegistered; + // (undocumented) genericDeviceClass: number; // (undocumented) isFrequentListening: FLiRS; @@ -21228,7 +22611,7 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC implements // (undocumented) protocolVersion: ProtocolVersion; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) specificDeviceClass: number; // (undocumented) @@ -21244,7 +22627,7 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC implements // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNewNodeRegisteredOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNewNodeRegisteredOptions extends CCCommandOptions, NodeInformationFrame { +export interface ZWaveProtocolCCNewNodeRegisteredOptions extends NodeInformationFrame { // (undocumented) newNodeId: number; } @@ -21253,11 +22636,13 @@ export interface ZWaveProtocolCCNewNodeRegisteredOptions extends CCCommandOption // // @public (undocumented) export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNewRangeRegisteredOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNewRangeRegistered; // (undocumented) neighborNodeIds: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) testedNodeId: number; } @@ -21265,7 +22650,7 @@ export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNewRangeRegisteredOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNewRangeRegisteredOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNewRangeRegisteredOptions { // (undocumented) neighborNodeIds: number[]; // (undocumented) @@ -21276,10 +22661,12 @@ export interface ZWaveProtocolCCNewRangeRegisteredOptions extends CCCommandOptio // // @public (undocumented) export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC implements NodeInformationFrame { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNodeInformationFrameOptions); + constructor(options: WithAddress_2); // (undocumented) basicDeviceClass: BasicDeviceClass; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNodeInformationFrame; + // (undocumented) genericDeviceClass: number; // (undocumented) isFrequentListening: FLiRS; @@ -21294,7 +22681,7 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC impleme // (undocumented) protocolVersion: ProtocolVersion; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) specificDeviceClass: number; // (undocumented) @@ -21310,26 +22697,28 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC impleme // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNodeInformationFrameOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNodeInformationFrameOptions extends CCCommandOptions, NodeInformationFrame { +export interface ZWaveProtocolCCNodeInformationFrameOptions extends NodeInformationFrame { } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNodesExist" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ZWaveProtocolCCNodesExist extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNodesExistOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNodesExist; // (undocumented) nodeIDs: number[]; // (undocumented) nodeMaskType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNodesExistOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNodesExistOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNodesExistOptions { // (undocumented) nodeIDs: number[]; // (undocumented) @@ -21340,19 +22729,21 @@ export interface ZWaveProtocolCCNodesExistOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCNodesExistReply extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNodesExistReplyOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNodesExistReply; // (undocumented) nodeListUpdated: boolean; // (undocumented) nodeMaskType: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNodesExistReplyOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNodesExistReplyOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNodesExistReplyOptions { // (undocumented) nodeListUpdated: boolean; // (undocumented) @@ -21363,17 +22754,19 @@ export interface ZWaveProtocolCCNodesExistReplyOptions extends CCCommandOptions // // @public (undocumented) export class ZWaveProtocolCCNOPPower extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCNOPPowerOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCNOPPower; // (undocumented) powerDampening: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCNOPPowerOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCNOPPowerOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNOPPowerOptions { // (undocumented) powerDampening: number; } @@ -21382,11 +22775,13 @@ export interface ZWaveProtocolCCNOPPowerOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCRangeInfoOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCRangeInfo; // (undocumented) neighborNodeIds: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) wakeUpTime?: WakeUpTime; } @@ -21394,7 +22789,7 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCRangeInfoOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCRangeInfoOptions extends CCCommandOptions { +export interface ZWaveProtocolCCRangeInfoOptions { // (undocumented) neighborNodeIds: number[]; // (undocumented) @@ -21411,17 +22806,19 @@ export class ZWaveProtocolCCRequestNodeInformationFrame extends ZWaveProtocolCC // // @public (undocumented) export class ZWaveProtocolCCReservedIDs extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCReservedIDsOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCReservedIDs; // (undocumented) reservedNodeIDs: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCReservedIDsOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCReservedIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCReservedIDsOptions { // (undocumented) reservedNodeIDs: number[]; } @@ -21430,17 +22827,19 @@ export interface ZWaveProtocolCCReservedIDsOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCReserveNodeIDs extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCReserveNodeIDsOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCReserveNodeIDs; // (undocumented) numNodeIDs: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCReserveNodeIDsOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCReserveNodeIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCReserveNodeIDsOptions { // (undocumented) numNodeIDs: number; } @@ -21449,11 +22848,13 @@ export interface ZWaveProtocolCCReserveNodeIDsOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCSetNWIModeOptions); + constructor(options: WithAddress_2); // (undocumented) enabled: boolean; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCSetNWIMode; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) timeoutMinutes?: number; } @@ -21461,7 +22862,7 @@ export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSetNWIModeOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCSetNWIModeOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetNWIModeOptions { // (undocumented) enabled: boolean; // (undocumented) @@ -21472,30 +22873,34 @@ export interface ZWaveProtocolCCSetNWIModeOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCSetSUC extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCSetSUCOptions); + constructor(options: WithAddress_2); // (undocumented) enableSIS: boolean; // (undocumented) - serialize(): Buffer; + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCSetSUC; + // (undocumented) + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSetSUCAck" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export class ZWaveProtocolCCSetSUCAck extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCSetSUCAckOptions); + constructor(options: WithAddress_2); // (undocumented) accepted: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCSetSUCAck; + // (undocumented) isSIS: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSetSUCAckOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCSetSUCAckOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetSUCAckOptions { // (undocumented) accepted: boolean; // (undocumented) @@ -21505,7 +22910,7 @@ export interface ZWaveProtocolCCSetSUCAckOptions extends CCCommandOptions { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSetSUCOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCSetSUCOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetSUCOptions { // (undocumented) enableSIS: boolean; } @@ -21514,17 +22919,19 @@ export interface ZWaveProtocolCCSetSUCOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCSmartStartIncludedNodeInformation extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCSmartStartIncludedNodeInformationOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCSmartStartIncludedNodeInformation; // (undocumented) nwiHomeId: Buffer; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSmartStartIncludedNodeInformationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCSmartStartIncludedNodeInformationOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSmartStartIncludedNodeInformationOptions { // (undocumented) nwiHomeId: Buffer; } @@ -21545,17 +22952,19 @@ export class ZWaveProtocolCCSmartStartPrime extends ZWaveProtocolCCNodeInformati // // @public (undocumented) export class ZWaveProtocolCCStaticRouteRequest extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCStaticRouteRequestOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCStaticRouteRequest; // (undocumented) nodeIds: number[]; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCStaticRouteRequestOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCStaticRouteRequestOptions extends CCCommandOptions { +export interface ZWaveProtocolCCStaticRouteRequestOptions { // (undocumented) nodeIds: number[]; } @@ -21564,11 +22973,13 @@ export interface ZWaveProtocolCCStaticRouteRequestOptions extends CCCommandOptio // // @public (undocumented) export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCSUCNodeIDOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCSUCNodeID; // (undocumented) isSIS: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) sucNodeId: number; } @@ -21576,7 +22987,7 @@ export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCSUCNodeIDOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCSUCNodeIDOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSUCNodeIDOptions { // (undocumented) isSIS: boolean; // (undocumented) @@ -21587,9 +22998,11 @@ export interface ZWaveProtocolCCSUCNodeIDOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCTransferEndOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCTransferEnd; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) status: NetworkTransferStatus; } @@ -21597,7 +23010,7 @@ export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCTransferEndOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCTransferEndOptions extends CCCommandOptions { +export interface ZWaveProtocolCCTransferEndOptions { // (undocumented) status: NetworkTransferStatus; } @@ -21606,17 +23019,19 @@ export interface ZWaveProtocolCCTransferEndOptions extends CCCommandOptions { // // @public (undocumented) export class ZWaveProtocolCCTransferNewPrimaryControllerComplete extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCTransferNewPrimaryControllerComplete; // (undocumented) genericDeviceClass: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; } // Warning: (ae-missing-release-tag) "ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions extends CCCommandOptions { +export interface ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions { // (undocumented) genericDeviceClass: number; } @@ -21625,10 +23040,12 @@ export interface ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions exte // // @public (undocumented) export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC implements NodeProtocolInfoAndDeviceClass { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCTransferNodeInformationOptions); + constructor(options: WithAddress_2); // (undocumented) basicDeviceClass: BasicDeviceClass; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCTransferNodeInformation; + // (undocumented) genericDeviceClass: number; // (undocumented) isFrequentListening: FLiRS; @@ -21645,7 +23062,7 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC impl // (undocumented) sequenceNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) sourceNodeId: number; // (undocumented) @@ -21661,7 +23078,7 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC impl // Warning: (ae-missing-release-tag) "ZWaveProtocolCCTransferNodeInformationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCTransferNodeInformationOptions extends CCCommandOptions, NodeProtocolInfoAndDeviceClass { +export interface ZWaveProtocolCCTransferNodeInformationOptions extends NodeProtocolInfoAndDeviceClass { // (undocumented) sequenceNumber: number; // (undocumented) @@ -21672,13 +23089,15 @@ export interface ZWaveProtocolCCTransferNodeInformationOptions extends CCCommand // // @public (undocumented) export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCTransferPresentationOptions); + constructor(options: WithAddress_2); // (undocumented) excludeNode: boolean; // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCTransferPresentation; + // (undocumented) includeNode: boolean; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) supportsNWI: boolean; } @@ -21686,7 +23105,7 @@ export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCTransferPresentationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCTransferPresentationOptions extends CCCommandOptions { +export interface ZWaveProtocolCCTransferPresentationOptions { // (undocumented) excludeNode: boolean; // (undocumented) @@ -21699,13 +23118,15 @@ export interface ZWaveProtocolCCTransferPresentationOptions extends CCCommandOpt // // @public (undocumented) export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { - constructor(host: ZWaveHost, options: CommandClassDeserializationOptions | ZWaveProtocolCCTransferRangeInformationOptions); + constructor(options: WithAddress_2); + // (undocumented) + static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCTransferRangeInformation; // (undocumented) neighborNodeIds: number[]; // (undocumented) sequenceNumber: number; // (undocumented) - serialize(): Buffer; + serialize(ctx: CCEncodingContext): Buffer; // (undocumented) testedNodeId: number; } @@ -21713,7 +23134,7 @@ export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { // Warning: (ae-missing-release-tag) "ZWaveProtocolCCTransferRangeInformationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveProtocolCCTransferRangeInformationOptions extends CCCommandOptions { +export interface ZWaveProtocolCCTransferRangeInformationOptions { // (undocumented) neighborNodeIds: number[]; // (undocumented) @@ -21800,8 +23221,8 @@ export enum ZWaveProtocolCommand { // Warnings were encountered during analysis: // -// src/cc/TransportServiceCC.ts:47:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "@zwave-js/cc" does not have an export "RELAXED_TIMING_THRESHOLD" -// src/cc/TransportServiceCC.ts:49:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "@zwave-js/cc" does not have an export "RELAXED_TIMING_THRESHOLD" +// src/cc/TransportServiceCC.ts:46:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "@zwave-js/cc" does not have an export "RELAXED_TIMING_THRESHOLD" +// src/cc/TransportServiceCC.ts:48:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "@zwave-js/cc" does not have an export "RELAXED_TIMING_THRESHOLD" // (No @packageDocumentation comment for this package) diff --git a/packages/cc/package.json b/packages/cc/package.json index 32d39158e138..65e1759e34c9 100644 --- a/packages/cc/package.json +++ b/packages/cc/package.json @@ -67,7 +67,6 @@ "dependencies": { "@zwave-js/core": "workspace:*", "@zwave-js/host": "workspace:*", - "@zwave-js/serial": "workspace:*", "@zwave-js/shared": "workspace:*", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3", diff --git a/packages/cc/src/cc/AlarmSensorCC.ts b/packages/cc/src/cc/AlarmSensorCC.ts index 5e4fcf8f4ea8..54a8b63e0a1f 100644 --- a/packages/cc/src/cc/AlarmSensorCC.ts +++ b/packages/cc/src/cc/AlarmSensorCC.ts @@ -1,29 +1,31 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, parseBitMask, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -129,12 +131,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, + endpointIndex: this.endpoint.index, sensorType, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -148,11 +150,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AlarmSensorCCSupportedReport >( cc, @@ -168,38 +170,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 +216,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 +232,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 +263,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 +280,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 +294,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 +307,7 @@ duration: ${currentValue.duration}`; } protected createMetadataForSensorType( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, sensorType: AlarmSensorType, ): void { const stateValue = AlarmSensorCCValues.state(sensorType); @@ -309,35 +315,59 @@ 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); } } +// @publicAPI +export interface AlarmSensorCCReportOptions { + sensorType: AlarmSensorType; + state: boolean; + severity?: number; + duration?: number; +} + @CCCommand(AlarmSensorCommand.Report) export class AlarmSensorCCReport extends AlarmSensorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, 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 - const sourceNodeId = this.payload[0]; - if (sourceNodeId !== 0) { - this.nodeId = sourceNodeId; - } - this.sensorType = this.payload[1]; + super(options); + + // TODO: Check implementation: + this.sensorType = options.sensorType; + this.state = options.state; + this.severity = options.severity; + this.duration = options.duration; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): AlarmSensorCCReport { + validatePayload(raw.payload.length >= 5, raw.payload[1] !== 0xff); + const sourceNodeId = raw.payload[0]; + + const sensorType: AlarmSensorType = raw.payload[1]; // Any positive value gets interpreted as alarm - this.state = this.payload[2] > 0; + const state: boolean = raw.payload[2] > 0; // Severity only ranges from 1 to 100 - if (this.payload[2] > 0 && this.payload[2] <= 0x64) { - this.severity = this.payload[2]; + let severity: number | undefined; + if (raw.payload[2] > 0 && raw.payload[2] <= 0x64) { + severity = raw.payload[2]; } + // ignore zero durations - this.duration = this.payload.readUInt16BE(3) || undefined; + const duration = raw.payload.readUInt16BE(3) || undefined; + + return new AlarmSensorCCReport({ + // Alarm Sensor reports may be forwarded by a different node, in this case + // (and only then!) the payload contains the original node ID + nodeId: sourceNodeId || ctx.sourceNodeId, + sensorType, + state, + severity, + duration, + }); } public readonly sensorType: AlarmSensorType; @@ -345,7 +375,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 +387,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; } @@ -391,7 +421,7 @@ function testResponseForAlarmSensorGet( } // @publicAPI -export interface AlarmSensorCCGetOptions extends CCCommandOptions { +export interface AlarmSensorCCGetOptions { sensorType?: AlarmSensorType; } @@ -399,31 +429,34 @@ export interface AlarmSensorCCGetOptions extends CCCommandOptions { @expectedCCResponse(AlarmSensorCCReport, testResponseForAlarmSensorGet) export class AlarmSensorCCGet extends AlarmSensorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | AlarmSensorCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.sensorType = options.sensorType ?? AlarmSensorType.Any; - } + super(options); + this.sensorType = options.sensorType ?? AlarmSensorType.Any; + } + + public static from(_raw: CCRaw, _ctx: CCParsingContext): AlarmSensorCCGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new AlarmSensorCCGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, @@ -434,42 +467,57 @@ export class AlarmSensorCCGet extends AlarmSensorCC { } } +// @publicAPI +export interface AlarmSensorCCSupportedReportOptions { + supportedSensorTypes: AlarmSensorType[]; +} + @CCCommand(AlarmSensorCommand.SupportedReport) export class AlarmSensorCCSupportedReport extends AlarmSensorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - const bitMaskLength = this.payload[0]; - validatePayload(this.payload.length >= 1 + bitMaskLength); - this._supportedSensorTypes = parseBitMask( - this.payload.subarray(1, 1 + bitMaskLength), + super(options); + + // TODO: Check implementation: + this.supportedSensorTypes = options.supportedSensorTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AlarmSensorCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const bitMaskLength = raw.payload[0]; + validatePayload(raw.payload.length >= 1 + bitMaskLength); + const supportedSensorTypes: AlarmSensorType[] = parseBitMask( + raw.payload.subarray(1, 1 + bitMaskLength), AlarmSensorType["General Purpose"], ); + + return new AlarmSensorCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedSensorTypes, + }); } - private _supportedSensorTypes: AlarmSensorType[]; @ccValue(AlarmSensorCCValues.supportedSensorTypes) - public get supportedSensorTypes(): readonly AlarmSensorType[] { - return this._supportedSensorTypes; - } + public supportedSensorTypes: AlarmSensorType[]; - 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); + for (const type of this.supportedSensorTypes) { + 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 + "supported sensor types": this.supportedSensorTypes .map((t) => getEnumMemberName(AlarmSensorType, t)) .join(", "), }, diff --git a/packages/cc/src/cc/AssociationCC.ts b/packages/cc/src/cc/AssociationCC.ts index b65b01750b44..276ba3732334 100644 --- a/packages/cc/src/cc/AssociationCC.ts +++ b/packages/cc/src/cc/AssociationCC.ts @@ -1,8 +1,9 @@ import type { - IZWaveEndpoint, + EndpointId, MaybeNotKnown, MessageRecord, SupervisionResult, + WithAddress, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -14,18 +15,19 @@ 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"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -103,11 +105,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationCCSupportedGroupingsReport >( cc, @@ -123,12 +125,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, + endpointIndex: this.endpoint.index, groupCount, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -139,12 +141,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, + endpointIndex: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -158,19 +160,19 @@ export class AssociationCCAPI extends PhysicalCCAPI { @validateArgs() public async sendReport( - options: AssociationCCReportSpecificOptions, + options: AssociationCCReportOptions, ): Promise { this.assertSupportsCommand( AssociationCommand, AssociationCommand.Report, ); - const cc = new AssociationCCReport(this.applHost, { + const cc = new AssociationCCReport({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -183,13 +185,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, + endpointIndex: this.endpoint.index, groupId, nodeIds, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -204,12 +206,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -251,11 +268,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationCCSpecificGroupReport >( cc, @@ -276,12 +293,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, + endpointIndex: this.endpoint.index, group, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -306,16 +323,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 +338,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 +352,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 +365,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 +386,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 +410,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 +433,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 +485,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", @@ -477,7 +496,7 @@ currently assigned nodes: ${group.nodeIds.map(String).join(", ")}`; } // @publicAPI -export interface AssociationCCSetOptions extends CCCommandOptions { +export interface AssociationCCSetOptions { groupId: number; nodeIds: number[]; } @@ -486,41 +505,46 @@ export interface AssociationCCSetOptions extends CCCommandOptions { @useSupervision() export class AssociationCCSet extends AssociationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | AssociationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.groupId = this.payload[0]; - this.nodeIds = [...this.payload.subarray(1)]; - } else { - if (options.groupId < 1) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { - throw new ZWaveError( - `All node IDs must be between 1 and ${MAX_NODES}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.groupId = options.groupId; - this.nodeIds = options.nodeIds; + super(options); + if (options.groupId < 1) { + throw new ZWaveError( + "The group id must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { + throw new ZWaveError( + `All node IDs must be between 1 and ${MAX_NODES}!`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.groupId = options.groupId; + this.nodeIds = options.nodeIds; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): AssociationCCSet { + validatePayload(raw.payload.length >= 2); + const groupId = raw.payload[0]; + const nodeIds = [...raw.payload.subarray(1)]; + + return new AssociationCCSet({ + nodeId: ctx.sourceNodeId, + groupId, + nodeIds, + }); } 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 +552,7 @@ export class AssociationCCSet extends AssociationCC { : "all nodes", }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -546,54 +570,43 @@ export interface AssociationCCRemoveOptions { @useSupervision() export class AssociationCCRemove extends AssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (AssociationCCRemoveOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - if (this.payload[0] !== 0) { - this.groupId = this.payload[0]; - } - 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, - ); - } + super(options); + // When removing associations, we allow invalid node IDs. + // See GH#3606 - it is possible that those exist. + this.groupId = options.groupId; + this.nodeIds = options.nodeIds; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): AssociationCCRemove { + validatePayload(raw.payload.length >= 1); - // When removing associations, we allow invalid node IDs. - // See GH#3606 - it is possible that those exist. - this.groupId = options.groupId; - this.nodeIds = options.nodeIds; + let groupId: number | undefined; + if (raw.payload[0] !== 0) { + groupId = raw.payload[0]; } + const nodeIds = [...raw.payload.subarray(1)]; + + return new AssociationCCRemove({ + nodeId: ctx.sourceNodeId, + groupId, + nodeIds, + }); } 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,14 +614,14 @@ export class AssociationCCRemove extends AssociationCC { : "all nodes", }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface AssociationCCReportSpecificOptions { +export interface AssociationCCReportOptions { groupId: number; maxNodes: number; nodeIds: number[]; @@ -618,25 +631,30 @@ export interface AssociationCCReportSpecificOptions { @CCCommand(AssociationCommand.Report) export class AssociationCCReport extends AssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (AssociationCCReportSpecificOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.groupId = this.payload[0]; - this.maxNodes = this.payload[1]; - this.reportsToFollow = this.payload[2]; - this.nodeIds = [...this.payload.subarray(3)]; - } else { - this.groupId = options.groupId; - this.maxNodes = options.maxNodes; - this.nodeIds = options.nodeIds; - this.reportsToFollow = options.reportsToFollow; - } + super(options); + + this.groupId = options.groupId; + this.maxNodes = options.maxNodes; + this.nodeIds = options.nodeIds; + this.reportsToFollow = options.reportsToFollow; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): AssociationCCReport { + validatePayload(raw.payload.length >= 3); + const groupId = raw.payload[0]; + const maxNodes = raw.payload[1]; + const reportsToFollow = raw.payload[2]; + const nodeIds = [...raw.payload.subarray(3)]; + + return new AssociationCCReport({ + nodeId: ctx.sourceNodeId, + groupId, + maxNodes, + reportsToFollow, + nodeIds, + }); } public groupId: number; @@ -665,8 +683,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 +692,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, @@ -698,7 +716,7 @@ export class AssociationCCReport extends AssociationCC { } // @publicAPI -export interface AssociationCCGetOptions extends CCCommandOptions { +export interface AssociationCCGetOptions { groupId: number; } @@ -706,75 +724,82 @@ export interface AssociationCCGetOptions extends CCCommandOptions { @expectedCCResponse(AssociationCCReport) export class AssociationCCGet extends AssociationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | AssociationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupId = this.payload[0]; - } else { - if (options.groupId < 1) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.groupId = options.groupId; + super(options); + if (options.groupId < 1) { + throw new ZWaveError( + "The group id must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); } + this.groupId = options.groupId; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): AssociationCCGet { + validatePayload(raw.payload.length >= 1); + const groupId = raw.payload[0]; + + return new AssociationCCGet({ + nodeId: ctx.sourceNodeId, + groupId, + }); } 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 }, }; } } // @publicAPI -export interface AssociationCCSupportedGroupingsReportOptions - extends CCCommandOptions -{ +export interface AssociationCCSupportedGroupingsReportOptions { groupCount: number; } @CCCommand(AssociationCommand.SupportedGroupingsReport) export class AssociationCCSupportedGroupingsReport extends AssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationCCSupportedGroupingsReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupCount = this.payload[0]; - } else { - this.groupCount = options.groupCount; - } + this.groupCount = options.groupCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationCCSupportedGroupingsReport { + validatePayload(raw.payload.length >= 1); + const groupCount = raw.payload[0]; + + return new AssociationCCSupportedGroupingsReport({ + nodeId: ctx.sourceNodeId, + groupCount, + }); } @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,31 +817,36 @@ export interface AssociationCCSpecificGroupReportOptions { @CCCommand(AssociationCommand.SpecificGroupReport) export class AssociationCCSpecificGroupReport extends AssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (AssociationCCSpecificGroupReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.group = this.payload[0]; - } else { - this.group = options.group; - } + this.group = options.group; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationCCSpecificGroupReport { + validatePayload(raw.payload.length >= 1); + const group = raw.payload[0]; + + return new AssociationCCSpecificGroupReport({ + nodeId: ctx.sourceNodeId, + group, + }); } 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..0f421077551b 100644 --- a/packages/cc/src/cc/AssociationGroupInfoCC.ts +++ b/packages/cc/src/cc/AssociationGroupInfoCC.ts @@ -1,28 +1,31 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type SupportsCC, + type WithAddress, encodeCCId, getCCName, parseCCId, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -109,12 +112,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, + endpointIndex: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCNameReport >( cc, @@ -130,14 +133,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, + endpointIndex: this.endpoint.index, groupId, name, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -148,13 +151,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, + endpointIndex: this.endpoint.index, groupId, refreshCache, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCInfoReport >( cc, @@ -174,20 +177,20 @@ export class AssociationGroupInfoCCAPI extends PhysicalCCAPI { @validateArgs() public async reportGroupInfo( - options: AssociationGroupInfoCCInfoReportSpecificOptions, + options: AssociationGroupInfoCCInfoReportOptions, ): Promise { this.assertSupportsCommand( AssociationGroupInfoCommand, AssociationGroupInfoCommand.InfoReport, ); - const cc = new AssociationGroupInfoCCInfoReport(this.applHost, { + const cc = new AssociationGroupInfoCCInfoReport({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -202,13 +205,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, + endpointIndex: this.endpoint.index, groupId, allowCache, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< AssociationGroupInfoCCCommandListReport >( cc, @@ -227,14 +230,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, + endpointIndex: this.endpoint.index, groupId, commands, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -255,11 +258,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 +273,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 +289,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 +303,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 +333,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 +375,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 +390,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 +398,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 +409,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 +455,7 @@ profile: ${ info.profile, ) }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -457,9 +466,7 @@ profile: ${ } // @publicAPI -export interface AssociationGroupInfoCCNameReportOptions - extends CCCommandOptions -{ +export interface AssociationGroupInfoCCNameReportOptions { groupId: number; name: string; } @@ -467,35 +474,41 @@ export interface AssociationGroupInfoCCNameReportOptions @CCCommand(AssociationGroupInfoCommand.NameReport) export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationGroupInfoCCNameReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.groupId = this.payload[0]; - const nameLength = this.payload[1]; - validatePayload(this.payload.length >= 2 + nameLength); - // The specs don't allow 0-terminated string, but some devices use them - // So we need to cut them off - this.name = cpp2js( - this.payload.subarray(2, 2 + nameLength).toString("utf8"), - ); - } else { - this.groupId = options.groupId; - this.name = options.name; - } + super(options); + + this.groupId = options.groupId; + this.name = options.name; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCNameReport { + validatePayload(raw.payload.length >= 2); + const groupId = raw.payload[0]; + const nameLength = raw.payload[1]; + validatePayload(raw.payload.length >= 2 + nameLength); + // The specs don't allow 0-terminated string, but some devices use them + // So we need to cut them off + const name = cpp2js( + raw.payload.subarray(2, 2 + nameLength).toString("utf8"), + ); + + return new AssociationGroupInfoCCNameReport({ + nodeId: ctx.sourceNodeId, + groupId, + name, + }); } 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 +520,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, @@ -527,7 +540,7 @@ export class AssociationGroupInfoCCNameReport extends AssociationGroupInfoCC { } // @publicAPI -export interface AssociationGroupInfoCCNameGetOptions extends CCCommandOptions { +export interface AssociationGroupInfoCCNameGetOptions { groupId: number; } @@ -535,30 +548,35 @@ export interface AssociationGroupInfoCCNameGetOptions extends CCCommandOptions { @expectedCCResponse(AssociationGroupInfoCCNameReport) export class AssociationGroupInfoCCNameGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationGroupInfoCCNameGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupId = this.payload[0]; - } else { - this.groupId = options.groupId; - } + super(options); + this.groupId = options.groupId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCNameGet { + validatePayload(raw.payload.length >= 1); + const groupId = raw.payload[0]; + + return new AssociationGroupInfoCCNameGet({ + nodeId: ctx.sourceNodeId, + groupId, + }); } 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 }, }; } @@ -572,7 +590,7 @@ export interface AssociationGroupInfo { } // @publicAPI -export interface AssociationGroupInfoCCInfoReportSpecificOptions { +export interface AssociationGroupInfoCCInfoReportOptions { isListMode: boolean; hasDynamicInfo: boolean; groups: AssociationGroupInfo[]; @@ -581,41 +599,43 @@ export interface AssociationGroupInfoCCInfoReportSpecificOptions { @CCCommand(AssociationGroupInfoCommand.InfoReport) export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ( - & AssociationGroupInfoCCInfoReportSpecificOptions - & CCCommandOptions - ), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.isListMode = !!(this.payload[0] & 0b1000_0000); - this.hasDynamicInfo = !!(this.payload[0] & 0b0100_0000); - - const groupCount = this.payload[0] & 0b0011_1111; - // each group requires 7 bytes of payload - validatePayload(this.payload.length >= 1 + groupCount * 7); - const _groups: AssociationGroupInfo[] = []; - for (let i = 0; i < groupCount; i++) { - const offset = 1 + i * 7; - // Parse the payload - const groupBytes = this.payload.subarray(offset, offset + 7); - const groupId = groupBytes[0]; - const mode = 0; // groupBytes[1]; - const profile = groupBytes.readUInt16BE(2); - const eventCode = 0; // groupBytes.readUInt16BE(5); - _groups.push({ groupId, mode, profile, eventCode }); - } - this.groups = _groups; - } else { - this.isListMode = options.isListMode; - this.hasDynamicInfo = options.hasDynamicInfo; - this.groups = options.groups; + super(options); + + this.isListMode = options.isListMode; + this.hasDynamicInfo = options.hasDynamicInfo; + this.groups = options.groups; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCInfoReport { + validatePayload(raw.payload.length >= 1); + const isListMode = !!(raw.payload[0] & 0b1000_0000); + const hasDynamicInfo = !!(raw.payload[0] & 0b0100_0000); + const groupCount = raw.payload[0] & 0b0011_1111; + // each group requires 7 bytes of payload + validatePayload(raw.payload.length >= 1 + groupCount * 7); + const groups: AssociationGroupInfo[] = []; + for (let i = 0; i < groupCount; i++) { + const offset = 1 + i * 7; + // Parse the payload + const groupBytes = raw.payload.subarray(offset, offset + 7); + const groupId = groupBytes[0]; + const mode = 0; // groupBytes[1]; + const profile = groupBytes.readUInt16BE(2); + const eventCode = 0; // groupBytes.readUInt16BE(5); + groups.push({ groupId, mode, profile, eventCode }); } + + return new AssociationGroupInfoCCInfoReport({ + nodeId: ctx.sourceNodeId, + isListMode, + hasDynamicInfo, + groups, + }); } public readonly isListMode: boolean; @@ -625,13 +645,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 +663,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 +677,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, @@ -684,7 +704,6 @@ export class AssociationGroupInfoCCInfoReport extends AssociationGroupInfoCC { // @publicAPI export type AssociationGroupInfoCCInfoGetOptions = - & CCCommandOptions & { refreshCache: boolean; } @@ -701,32 +720,41 @@ export type AssociationGroupInfoCCInfoGetOptions = @expectedCCResponse(AssociationGroupInfoCCInfoReport) export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationGroupInfoCCInfoGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - const optionByte = this.payload[0]; - this.refreshCache = !!(optionByte & 0b1000_0000); - this.listMode = !!(optionByte & 0b0100_0000); - if (!this.listMode) { - this.groupId = this.payload[1]; - } - } else { - this.refreshCache = options.refreshCache; - if ("listMode" in options) this.listMode = options.listMode; - if ("groupId" in options) this.groupId = options.groupId; + super(options); + this.refreshCache = options.refreshCache; + if ("listMode" in options) this.listMode = options.listMode; + if ("groupId" in options) this.groupId = options.groupId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCInfoGet { + validatePayload(raw.payload.length >= 2); + const optionByte = raw.payload[0]; + const refreshCache = !!(optionByte & 0b1000_0000); + const listMode: boolean | undefined = !!(optionByte & 0b0100_0000); + let groupId: number | undefined; + + if (!listMode) { + groupId = raw.payload[1]; } + + return new AssociationGroupInfoCCInfoGet({ + nodeId: ctx.sourceNodeId, + refreshCache, + listMode, + groupId, + }); } public refreshCache: boolean; 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 +762,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,16 +775,14 @@ export class AssociationGroupInfoCCInfoGet extends AssociationGroupInfoCC { } message["refresh cache"] = this.refreshCache; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface AssociationGroupInfoCCCommandListReportOptions - extends CCCommandOptions -{ +export interface AssociationGroupInfoCCCommandListReportOptions { groupId: number; commands: ReadonlyMap; } @@ -766,35 +792,39 @@ export class AssociationGroupInfoCCCommandListReport extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationGroupInfoCCCommandListReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.groupId = this.payload[0]; - const listLength = this.payload[1]; - validatePayload(this.payload.length >= 2 + listLength); - const listBytes = this.payload.subarray(2, 2 + listLength); - // Parse all CC ids and commands - let offset = 0; - const commands = new Map(); - while (offset < listLength) { - const { ccId, bytesRead } = parseCCId(listBytes, offset); - const command = listBytes[offset + bytesRead]; - if (!commands.has(ccId)) commands.set(ccId, []); - commands.get(ccId)!.push(command); - offset += bytesRead + 1; - } - - this.commands = commands; - } else { - this.groupId = options.groupId; - this.commands = options.commands; + super(options); + + this.groupId = options.groupId; + this.commands = options.commands; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCCommandListReport { + validatePayload(raw.payload.length >= 2); + const groupId = raw.payload[0]; + const listLength = raw.payload[1]; + validatePayload(raw.payload.length >= 2 + listLength); + const listBytes = raw.payload.subarray(2, 2 + listLength); + // Parse all CC ids and commands + let offset = 0; + const commands = new Map(); + while (offset < listLength) { + const { ccId, bytesRead } = parseCCId(listBytes, offset); + const command = listBytes[offset + bytesRead]; + if (!commands.has(ccId)) commands.set(ccId, []); + commands.get(ccId)!.push(command); + offset += bytesRead + 1; } + + return new AssociationGroupInfoCCCommandListReport({ + nodeId: ctx.sourceNodeId, + groupId, + commands, + }); } public readonly groupId: number; @@ -806,7 +836,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 +851,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: `${ @@ -846,9 +876,7 @@ export class AssociationGroupInfoCCCommandListReport } // @publicAPI -export interface AssociationGroupInfoCCCommandListGetOptions - extends CCCommandOptions -{ +export interface AssociationGroupInfoCCCommandListGetOptions { allowCache: boolean; groupId: number; } @@ -859,36 +887,42 @@ export class AssociationGroupInfoCCCommandListGet extends AssociationGroupInfoCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | AssociationGroupInfoCCCommandListGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.allowCache = !!(this.payload[0] & 0b1000_0000); - this.groupId = this.payload[1]; - } else { - this.allowCache = options.allowCache; - this.groupId = options.groupId; - } + super(options); + this.allowCache = options.allowCache; + this.groupId = options.groupId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): AssociationGroupInfoCCCommandListGet { + validatePayload(raw.payload.length >= 2); + const allowCache = !!(raw.payload[0] & 0b1000_0000); + const groupId = raw.payload[1]; + + return new AssociationGroupInfoCCCommandListGet({ + nodeId: ctx.sourceNodeId, + allowCache, + groupId, + }); } 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..6e7e570d42b7 100644 --- a/packages/cc/src/cc/BarrierOperatorCC.ts +++ b/packages/cc/src/cc/BarrierOperatorCC.ts @@ -7,6 +7,7 @@ import { type SupervisionResult, UNKNOWN_STATE, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -15,9 +16,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, @@ -40,10 +41,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -146,11 +148,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BarrierOperatorCCReport >( cc, @@ -170,12 +172,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, + endpointIndex: this.endpoint.index, targetState, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -187,14 +189,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, + endpointIndex: this.endpoint.index, + }); + const response = await this.host.sendCommand< BarrierOperatorCCSignalingCapabilitiesReport >( cc, @@ -212,12 +211,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, + endpointIndex: this.endpoint.index, subsystemType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BarrierOperatorCCEventSignalingReport >( cc, @@ -236,14 +235,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, + endpointIndex: 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 +352,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 +421,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 +466,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 +480,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 +509,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 +520,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 +532,7 @@ export class BarrierOperatorCC extends CommandClass { } } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: "querying current barrier state...", direction: "outbound", }); @@ -538,7 +541,7 @@ export class BarrierOperatorCC extends CommandClass { } // @publicAPI -export interface BarrierOperatorCCSetOptions extends CCCommandOptions { +export interface BarrierOperatorCCSetOptions { targetState: BarrierState.Open | BarrierState.Closed; } @@ -546,73 +549,98 @@ export interface BarrierOperatorCCSetOptions extends CCCommandOptions { @useSupervision() export class BarrierOperatorCCSet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | BarrierOperatorCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.targetState = options.targetState; - } + super(options); + this.targetState = options.targetState; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): BarrierOperatorCCSet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new BarrierOperatorCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 }, }; } } +// @publicAPI +export interface BarrierOperatorCCReportOptions { + position: MaybeUnknown; + currentState: MaybeUnknown; +} + @CCCommand(BarrierOperatorCommand.Report) export class BarrierOperatorCCReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); + // TODO: Check implementation: + this.position = options.position; + this.currentState = options.currentState; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BarrierOperatorCCReport { + validatePayload(raw.payload.length >= 1); // The payload byte encodes information about the state and position in a single value - const payloadValue = this.payload[0]; + const payloadValue = raw.payload[0]; + let position: MaybeUnknown; if (payloadValue <= 99) { // known position - this.position = payloadValue; + position = payloadValue; } else if (payloadValue === 255) { // known position, fully opened - this.position = 100; + position = 100; } else { // unknown position - this.position = UNKNOWN_STATE; + position = UNKNOWN_STATE; } + let currentState: MaybeUnknown; if ( payloadValue === BarrierState.Closed || payloadValue >= BarrierState.Closing ) { // predefined states - this.currentState = payloadValue; + currentState = payloadValue; } else if (payloadValue > 0 && payloadValue <= 99) { // stopped at exact position - this.currentState = BarrierState.Stopped; + currentState = BarrierState.Stopped; } else { // invalid value, assume unknown - this.currentState = UNKNOWN_STATE; + currentState = UNKNOWN_STATE; } + + return new BarrierOperatorCCReport({ + nodeId: ctx.sourceNodeId, + position, + currentState, + }); } @ccValue(BarrierOperatorCCValues.currentState) @@ -621,9 +649,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 @@ -638,28 +666,47 @@ export class BarrierOperatorCCReport extends BarrierOperatorCC { @expectedCCResponse(BarrierOperatorCCReport) export class BarrierOperatorCCGet extends BarrierOperatorCC {} +// @publicAPI +export interface BarrierOperatorCCSignalingCapabilitiesReportOptions { + supportedSubsystemTypes: SubsystemType[]; +} + @CCCommand(BarrierOperatorCommand.SignalingCapabilitiesReport) export class BarrierOperatorCCSignalingCapabilitiesReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress< + BarrierOperatorCCSignalingCapabilitiesReportOptions + >, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.supportedSubsystemTypes = options.supportedSubsystemTypes; + } - this.supportedSubsystemTypes = parseBitMask( - this.payload, + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BarrierOperatorCCSignalingCapabilitiesReport { + const supportedSubsystemTypes: SubsystemType[] = parseBitMask( + raw.payload, SubsystemType.Audible, ); + + return new BarrierOperatorCCSignalingCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + supportedSubsystemTypes, + }); } @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)}`) @@ -676,9 +723,7 @@ export class BarrierOperatorCCSignalingCapabilitiesGet {} // @publicAPI -export interface BarrierOperatorCCEventSignalingSetOptions - extends CCCommandOptions -{ +export interface BarrierOperatorCCEventSignalingSetOptions { subsystemType: SubsystemType; subsystemState: SubsystemState; } @@ -687,34 +732,39 @@ export interface BarrierOperatorCCEventSignalingSetOptions @useSupervision() export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | BarrierOperatorCCEventSignalingSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.subsystemType = options.subsystemType; - this.subsystemState = options.subsystemState; - } + super(options); + this.subsystemType = options.subsystemType; + this.subsystemState = options.subsystemState; } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): BarrierOperatorCCEventSignalingSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new BarrierOperatorCCEventSignalingSet({ + // nodeId: ctx.sourceNodeId, + // }); + } + 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, @@ -729,28 +779,48 @@ export class BarrierOperatorCCEventSignalingSet extends BarrierOperatorCC { } } +// @publicAPI +export interface BarrierOperatorCCEventSignalingReportOptions { + subsystemType: SubsystemType; + subsystemState: SubsystemState; +} + @CCCommand(BarrierOperatorCommand.EventSignalingReport) export class BarrierOperatorCCEventSignalingReport extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); - this.subsystemType = this.payload[0]; - this.subsystemState = this.payload[1]; + // TODO: Check implementation: + this.subsystemType = options.subsystemType; + this.subsystemState = options.subsystemState; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BarrierOperatorCCEventSignalingReport { + validatePayload(raw.payload.length >= 2); + const subsystemType: SubsystemType = raw.payload[0]; + const subsystemState: SubsystemState = raw.payload[1]; + + return new BarrierOperatorCCEventSignalingReport({ + nodeId: ctx.sourceNodeId, + subsystemType, + subsystemState, + }); + } + + 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 +828,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, @@ -776,9 +846,7 @@ export class BarrierOperatorCCEventSignalingReport extends BarrierOperatorCC { } // @publicAPI -export interface BarrierOperatorCCEventSignalingGetOptions - extends CCCommandOptions -{ +export interface BarrierOperatorCCEventSignalingGetOptions { subsystemType: SubsystemType; } @@ -786,33 +854,37 @@ export interface BarrierOperatorCCEventSignalingGetOptions @expectedCCResponse(BarrierOperatorCCEventSignalingReport) export class BarrierOperatorCCEventSignalingGet extends BarrierOperatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | BarrierOperatorCCEventSignalingGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.subsystemType = options.subsystemType; - } + super(options); + this.subsystemType = options.subsystemType; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): BarrierOperatorCCEventSignalingGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new BarrierOperatorCCEventSignalingGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..f25235ed58ba 100644 --- a/packages/cc/src/cc/BasicCC.ts +++ b/packages/cc/src/cc/BasicCC.ts @@ -1,24 +1,33 @@ 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, + type WithAddress, maybeUnknownToString, parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; -import { type AllOrNone, pick } from "@zwave-js/shared/safe"; +import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -32,10 +41,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -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, + endpointIndex: 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, + endpointIndex: 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)); @@ -349,7 +370,7 @@ remaining duration: ${basicResponse.duration?.toString() ?? "undefined"}`; } // @publicAPI -export interface BasicCCSetOptions extends CCCommandOptions { +export interface BasicCCSetOptions { targetValue: number; } @@ -357,79 +378,84 @@ export interface BasicCCSetOptions extends CCCommandOptions { @useSupervision() export class BasicCCSet extends BasicCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | BasicCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.targetValue = this.payload[0]; - } else { - this.targetValue = options.targetValue; - } + super(options); + this.targetValue = options.targetValue; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): BasicCCSet { + validatePayload(raw.payload.length >= 1); + const targetValue = raw.payload[0]; + + return new BasicCCSet({ + nodeId: ctx.sourceNodeId, + targetValue, + }); } 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 }, }; } } // @publicAPI -export type BasicCCReportOptions = - & CCCommandOptions - & { - currentValue: number; - } - & AllOrNone<{ - targetValue: number; - duration: Duration; - }>; +export interface BasicCCReportOptions { + currentValue?: MaybeUnknown; + targetValue?: MaybeUnknown; + duration?: Duration; +} @CCCommand(BasicCommand.Report) export class BasicCCReport extends BasicCC { // @noCCValues See comment in the constructor public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | BasicCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this._currentValue = - // 0xff is a legacy value for 100% (99) - this.payload[0] === 0xff - ? 99 - : parseMaybeNumber(this.payload[0]); - - if (this.payload.length >= 3) { - this.targetValue = parseMaybeNumber(this.payload[1]); - this.duration = Duration.parseReport(this.payload[2]); - } - } else { - this._currentValue = options.currentValue; - if ("targetValue" in options) { - this.targetValue = options.targetValue; - this.duration = options.duration; - } + super(options); + + this.currentValue = options.currentValue; + this.targetValue = options.targetValue; + this.duration = options.duration; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): BasicCCReport { + validatePayload(raw.payload.length >= 1); + const currentValue: MaybeUnknown | undefined = + // 0xff is a legacy value for 100% (99) + raw.payload[0] === 0xff + ? 99 + : parseMaybeNumber(raw.payload[0]); + validatePayload(currentValue !== undefined); + + let targetValue: MaybeUnknown | undefined; + let duration: Duration | undefined; + + if (raw.payload.length >= 3) { + targetValue = parseMaybeNumber(raw.payload[1]); + duration = Duration.parseReport(raw.payload[2]); } + + return new BasicCCReport({ + nodeId: ctx.sourceNodeId, + currentValue, + targetValue, + duration, + }); } - private _currentValue: MaybeUnknown | undefined; @ccValue(BasicCCValues.currentValue) - public get currentValue(): MaybeUnknown | undefined { - return this._currentValue; - } + public currentValue: MaybeUnknown | undefined; @ccValue(BasicCCValues.targetValue) public readonly targetValue: MaybeUnknown | undefined; @@ -437,14 +463,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 +483,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 +506,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 +523,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 +537,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..ecae5e26e410 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 WithAddress, 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,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 { type AllOrNone, getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { @@ -27,10 +35,11 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -228,11 +237,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -256,11 +265,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -276,34 +285,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 +328,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 +343,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 +363,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 +374,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( @@ -381,7 +400,6 @@ temperature: ${batteryHealth.temperature} °C`; // @publicAPI export type BatteryCCReportOptions = - & CCCommandOptions & ( | { isLow?: false; @@ -410,59 +428,83 @@ export type BatteryCCReportOptions = @CCCommand(BatteryCommand.Report) export class BatteryCCReport extends BatteryCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | BatteryCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.level = this.payload[0]; - if (this.level === 0xff) { - this.level = 0; - this.isLow = true; - } else { - this.isLow = false; - } + super(options); + + this.level = options.isLow ? 0 : options.level; + this.isLow = !!options.isLow; + this.chargingStatus = options.chargingStatus; + this.rechargeable = options.rechargeable; + this.backup = options.backup; + this.overheating = options.overheating; + this.lowFluid = options.lowFluid; + this.rechargeOrReplace = options.rechargeOrReplace; + this.disconnected = options.disconnected; + this.lowTemperatureStatus = options.lowTemperatureStatus; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): BatteryCCReport { + let ccOptions: BatteryCCReportOptions; - if (this.payload.length >= 3) { - // Starting with V2 - this.chargingStatus = this.payload[1] >>> 6; - this.rechargeable = !!(this.payload[1] & 0b0010_0000); - this.backup = !!(this.payload[1] & 0b0001_0000); - this.overheating = !!(this.payload[1] & 0b1000); - this.lowFluid = !!(this.payload[1] & 0b0100); - this.rechargeOrReplace = !!(this.payload[1] & 0b10) + validatePayload(raw.payload.length >= 1); + const level = raw.payload[0]; + + if (level === 0xff) { + ccOptions = { + isLow: true, + }; + } else { + ccOptions = { + isLow: false, + level, + }; + } + + if (raw.payload.length >= 3) { + // Starting with V2 + const chargingStatus: BatteryChargingStatus = raw.payload[1] >>> 6; + const rechargeable = !!(raw.payload[1] & 0b0010_0000); + const backup = !!(raw.payload[1] & 0b0001_0000); + const overheating = !!(raw.payload[1] & 0b1000); + const lowFluid = !!(raw.payload[1] & 0b0100); + const rechargeOrReplace: BatteryReplacementStatus = + !!(raw.payload[1] & 0b10) ? BatteryReplacementStatus.Now - : !!(this.payload[1] & 0b1) + : !!(raw.payload[1] & 0b1) ? BatteryReplacementStatus.Soon : BatteryReplacementStatus.No; - this.lowTemperatureStatus = !!(this.payload[2] & 0b10); - this.disconnected = !!(this.payload[2] & 0b1); - } - } else { - this.level = options.isLow ? 0 : options.level; - this.isLow = !!options.isLow; - this.chargingStatus = options.chargingStatus; - this.rechargeable = options.rechargeable; - this.backup = options.backup; - this.overheating = options.overheating; - this.lowFluid = options.lowFluid; - this.rechargeOrReplace = options.rechargeOrReplace; - this.disconnected = options.disconnected; - this.lowTemperatureStatus = options.lowTemperatureStatus; + const lowTemperatureStatus = !!(raw.payload[2] & 0b10); + const disconnected = !!(raw.payload[2] & 0b1); + + ccOptions = { + ...ccOptions, + chargingStatus, + rechargeable, + backup, + overheating, + lowFluid, + rechargeOrReplace, + lowTemperatureStatus, + disconnected, + }; } + + return new BatteryCCReport({ + nodeId: ctx.sourceNodeId, + ...ccOptions, + }); } - 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 +521,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 +564,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 +586,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 +625,7 @@ export class BatteryCCReport extends BatteryCC { message.disconnected = this.disconnected; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -593,34 +635,56 @@ export class BatteryCCReport extends BatteryCC { @expectedCCResponse(BatteryCCReport) export class BatteryCCGet extends BatteryCC {} +// @publicAPI +export interface BatteryCCHealthReportOptions { + maximumCapacity?: number; + temperature?: number; + temperatureScale?: number; +} + @CCCommand(BatteryCommand.HealthReport) export class BatteryCCHealthReport extends BatteryCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); + // TODO: Check implementation: + this.maximumCapacity = options.maximumCapacity; + this.temperature = options.temperature; + this.temperatureScale = options.temperatureScale; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BatteryCCHealthReport { + validatePayload(raw.payload.length >= 2); // Parse maximum capacity. 0xff means unknown - this.maximumCapacity = this.payload[0]; - if (this.maximumCapacity === 0xff) this.maximumCapacity = undefined; - - const { value: temperature, scale } = parseFloatWithScale( - this.payload.subarray(1), + let maximumCapacity: number | undefined = raw.payload[0]; + if (maximumCapacity === 0xff) maximumCapacity = undefined; + const { + value: temperature, + scale: temperatureScale, + } = parseFloatWithScale( + raw.payload.subarray(1), true, // The temperature field may be omitted ); - this.temperature = temperature; - this.temperatureScale = scale; + + return new BatteryCCHealthReport({ + nodeId: ctx.sourceNodeId, + maximumCapacity, + temperature, + temperatureScale, + }); } - 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 +700,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..e1f915135c81 100644 --- a/packages/cc/src/cc/BinarySensorCC.ts +++ b/packages/cc/src/cc/BinarySensorCC.ts @@ -1,19 +1,20 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, encodeBitMask, parseBitMask, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, isEnumMember } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -25,10 +26,11 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -111,12 +113,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, + endpointIndex: this.endpoint.index, sensorType, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -134,13 +136,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, + endpointIndex: 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 +153,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< BinarySensorCCSupportedReport >( cc, @@ -174,12 +176,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, + endpointIndex: this.endpoint.index, supportedSensorTypes: supported, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -189,26 +191,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 +227,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 +243,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 +280,7 @@ export class BinarySensorCC extends CommandClass { } else { const supportedSensorTypes: readonly BinarySensorType[] = this.getValue( - applHost, + ctx, BinarySensorCCValues.supportedSensorTypes, ) ?? []; @@ -284,14 +290,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 +313,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 +326,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, ); @@ -333,7 +339,7 @@ export class BinarySensorCC extends CommandClass { } // @publicAPI -export interface BinarySensorCCReportOptions extends CCCommandOptions { +export interface BinarySensorCCReportOptions { type?: BinarySensorType; value: boolean; } @@ -341,34 +347,41 @@ export interface BinarySensorCCReportOptions extends CCCommandOptions { @CCCommand(BinarySensorCommand.Report) export class BinarySensorCCReport extends BinarySensorCC { public constructor( - host: ZWaveHost, - options: - | BinarySensorCCReportOptions - | CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.value = this.payload[0] === 0xff; - this.type = BinarySensorType.Any; - if (this.payload.length >= 2) { - this.type = this.payload[1]; - } - } else { - this.type = options.type ?? BinarySensorType.Any; - this.value = options.value; + super(options); + + this.type = options.type ?? BinarySensorType.Any; + this.value = options.value; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BinarySensorCCReport { + validatePayload(raw.payload.length >= 1); + const value = raw.payload[0] === 0xff; + let type: BinarySensorType = BinarySensorType.Any; + + if (raw.payload.length >= 2) { + type = raw.payload[1]; } + + return new BinarySensorCCReport({ + nodeId: ctx.sourceNodeId, + value, + type, + }); } - 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 +390,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 +399,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, @@ -417,7 +430,7 @@ function testResponseForBinarySensorGet( } // @publicAPI -export interface BinarySensorCCGetOptions extends CCCommandOptions { +export interface BinarySensorCCGetOptions { sensorType?: BinarySensorType; } @@ -425,29 +438,35 @@ export interface BinarySensorCCGetOptions extends CCCommandOptions { @expectedCCResponse(BinarySensorCCReport, testResponseForBinarySensorGet) export class BinarySensorCCGet extends BinarySensorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | BinarySensorCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length >= 1) { - this.sensorType = this.payload[0]; - } - } else { - this.sensorType = options.sensorType; + super(options); + this.sensorType = options.sensorType; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): BinarySensorCCGet { + let sensorType: BinarySensorType | undefined; + + if (raw.payload.length >= 1) { + sensorType = raw.payload[0]; } + + return new BinarySensorCCGet({ + nodeId: ctx.sourceNodeId, + sensorType, + }); } 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,40 +485,49 @@ export interface BinarySensorCCSupportedReportOptions { @CCCommand(BinarySensorCommand.SupportedReport) export class BinarySensorCCSupportedReport extends BinarySensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (BinarySensorCCSupportedReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); + + this.supportedSensorTypes = options.supportedSensorTypes; + } - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - // The enumeration starts at 1, but the first (reserved) bit is included - // in the report - this.supportedSensorTypes = parseBitMask(this.payload, 0).filter( + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BinarySensorCCSupportedReport { + validatePayload(raw.payload.length >= 1); + // The enumeration starts at 1, but the first (reserved) bit is included + // in the report + const supportedSensorTypes: BinarySensorType[] = parseBitMask( + raw.payload, + 0, + ) + .filter( (t) => t !== 0, ); - } else { - this.supportedSensorTypes = options.supportedSensorTypes; - } + + return new BinarySensorCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedSensorTypes, + }); } @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..64be97c29aa1 100644 --- a/packages/cc/src/cc/BinarySwitchCC.ts +++ b/packages/cc/src/cc/BinarySwitchCC.ts @@ -9,17 +9,17 @@ import { type SupervisionResult, UNKNOWN_STATE, ValueMetadata, + type WithAddress, encodeMaybeBoolean, maybeUnknownToString, parseMaybeBoolean, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; -import type { AllOrNone } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -33,10 +33,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -100,11 +101,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -133,13 +134,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, + endpointIndex: 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 +195,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 +240,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 +286,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,16 +295,16 @@ 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; } } // @publicAPI -export interface BinarySwitchCCSetOptions extends CCCommandOptions { +export interface BinarySwitchCCSetOptions { targetValue: boolean; duration?: Duration | string; } @@ -308,33 +313,41 @@ export interface BinarySwitchCCSetOptions extends CCCommandOptions { @useSupervision() export class BinarySwitchCCSet extends BinarySwitchCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | BinarySwitchCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.targetValue = !!this.payload[0]; - if (this.payload.length >= 2) { - this.duration = Duration.parseSet(this.payload[1]); - } - } else { - this.targetValue = options.targetValue; - this.duration = Duration.from(options.duration); + super(options); + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } + + public static from(raw: CCRaw, ctx: CCParsingContext): BinarySwitchCCSet { + validatePayload(raw.payload.length >= 1); + const targetValue = !!raw.payload[0]; + let duration: Duration | undefined; + + if (raw.payload.length >= 2) { + duration = Duration.parseSet(raw.payload[1]); } + + return new BinarySwitchCCSet({ + nodeId: ctx.sourceNodeId, + targetValue, + duration, + }); } 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 +355,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,46 +366,54 @@ export class BinarySwitchCCSet extends BinarySwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export type BinarySwitchCCReportOptions = - & CCCommandOptions - & { - currentValue: MaybeUnknown; - } - & AllOrNone<{ - targetValue: MaybeUnknown; - duration: Duration | string; - }>; +export interface BinarySwitchCCReportOptions { + currentValue?: MaybeUnknown; + targetValue?: MaybeUnknown; + duration?: Duration | string; +} @CCCommand(BinarySwitchCommand.Report) export class BinarySwitchCCReport extends BinarySwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | BinarySwitchCCReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.currentValue = parseMaybeBoolean(this.payload[0]); + this.currentValue = options.currentValue; + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } - if (this.payload.length >= 3) { - this.targetValue = parseMaybeBoolean(this.payload[1]); - this.duration = Duration.parseReport(this.payload[2]); - } - } else { - this.currentValue = options.currentValue; - this.targetValue = options.targetValue; - this.duration = Duration.from(options.duration); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): BinarySwitchCCReport { + validatePayload(raw.payload.length >= 1); + const currentValue: MaybeUnknown | undefined = + parseMaybeBoolean( + raw.payload[0], + ); + let targetValue: MaybeUnknown | undefined; + let duration: Duration | undefined; + + if (raw.payload.length >= 3) { + targetValue = parseMaybeBoolean(raw.payload[1]); + duration = Duration.parseReport(raw.payload[2]); } + + return new BinarySwitchCCReport({ + nodeId: ctx.sourceNodeId, + currentValue, + targetValue, + duration, + }); } @ccValue(BinarySwitchCCValues.currentValue) @@ -404,7 +425,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 +438,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 +452,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..d8368a895400 100644 --- a/packages/cc/src/cc/CRC16CC.ts +++ b/packages/cc/src/cc/CRC16CC.ts @@ -4,16 +4,16 @@ import { EncapsulationFlags, type MaybeNotKnown, type MessageOrCCLogEntry, + type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { + CCEncodingContext, + CCParsingContext, + GetValueDB, +} from "@zwave-js/host/safe"; import { CCAPI } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -21,8 +21,14 @@ import { expectedCCResponse, implementedVersion, } from "../lib/CommandClassDecorators"; + import { CRC16Command } from "../lib/_Types"; +const headerBuffer = Buffer.from([ + CommandClasses["CRC-16 Encapsulation"], + CRC16Command.CommandEncapsulation, +]); + // @noSetValueAPI // @noInterview This CC only has a single encapsulation command @@ -46,12 +52,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, + endpointIndex: this.endpoint.index, encapsulated: encapsulatedCC, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -70,10 +76,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, }); @@ -88,7 +93,7 @@ export class CRC16CC extends CommandClass { } // @publicAPI -export interface CRC16CCCommandEncapsulationOptions extends CCCommandOptions { +export interface CRC16CCCommandEncapsulationOptions { encapsulated: CommandClass; } @@ -107,53 +112,50 @@ function getCCResponseForCommandEncapsulation( ) export class CRC16CCCommandEncapsulation extends CRC16CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | CRC16CCCommandEncapsulationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - - const ccBuffer = this.payload.subarray(0, -2); - - // Verify the CRC - let expectedCRC = CRC16_CCITT(this.headerBuffer); - expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); - const actualCRC = this.payload.readUInt16BE( - this.payload.length - 2, - ); - validatePayload(expectedCRC === actualCRC); - - this.encapsulated = CommandClass.from(this.host, { - data: ccBuffer, - fromEncapsulation: true, - encapCC: this, - origin: options.origin, - frameType: options.frameType, - }); - } else { - this.encapsulated = options.encapsulated; - options.encapsulated.encapsulatingCC = this as any; - } + super(options); + this.encapsulated = options.encapsulated; + this.encapsulated.encapsulatingCC = this as any; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): CRC16CCCommandEncapsulation { + validatePayload(raw.payload.length >= 3); + + const ccBuffer = raw.payload.subarray(0, -2); + + // Verify the CRC + let expectedCRC = CRC16_CCITT(headerBuffer); + expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); + const actualCRC = raw.payload.readUInt16BE( + raw.payload.length - 2, + ); + validatePayload(expectedCRC === actualCRC); + + const encapsulated = CommandClass.parse(ccBuffer, ctx); + return new CRC16CCCommandEncapsulation({ + nodeId: ctx.sourceNodeId, + encapsulated, + }); } 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)]); // Compute and save the CRC16 in the payload // The CC header is included in the CRC computation - let crc = CRC16_CCITT(this.headerBuffer); + let crc = CRC16_CCITT(headerBuffer); 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 +163,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..8543402458fa 100644 --- a/packages/cc/src/cc/CentralSceneCC.ts +++ b/packages/cc/src/cc/CentralSceneCC.ts @@ -6,6 +6,7 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -15,9 +16,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -32,10 +33,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, } from "../lib/CommandClass"; import { API, @@ -113,11 +114,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< CentralSceneCCSupportedReport >( cc, @@ -139,11 +140,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< CentralSceneCCConfigurationReport >( cc, @@ -163,12 +164,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, + endpointIndex: 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 +219,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 +242,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 +259,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 +269,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 +285,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 +295,62 @@ supports slow refresh: ${ccSupported.supportsSlowRefresh}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } +// @publicAPI +export interface CentralSceneCCNotificationOptions { + sequenceNumber: number; + keyAttribute: CentralSceneKeys; + sceneNumber: number; + slowRefresh?: boolean; +} + @CCCommand(CentralSceneCommand.Notification) export class CentralSceneCCNotification extends CentralSceneCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, 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 - ) { + super(options); + + // TODO: Check implementation: + this.sequenceNumber = options.sequenceNumber; + this.keyAttribute = options.keyAttribute; + this.sceneNumber = options.sceneNumber; + this.slowRefresh = options.slowRefresh; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): CentralSceneCCNotification { + validatePayload(raw.payload.length >= 3); + const sequenceNumber = raw.payload[0]; + const keyAttribute: CentralSceneKeys = raw.payload[1] & 0b111; + const sceneNumber = raw.payload[2]; + let slowRefresh: boolean | undefined; + if (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); + slowRefresh = !!(raw.payload[1] & 0b1000_0000); } + + return new CentralSceneCCNotification({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + keyAttribute, + sceneNumber, + slowRefresh, + }); } - 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 +364,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,58 +377,90 @@ export class CentralSceneCCNotification extends CentralSceneCC { message["slow refresh"] = this.slowRefresh; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface CentralSceneCCSupportedReportOptions { + sceneCount: number; + supportsSlowRefresh: MaybeNotKnown; + supportedKeyAttributes: Record; +} + @CCCommand(CentralSceneCommand.SupportedReport) export class CentralSceneCCSupportedReport extends CentralSceneCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - - validatePayload(this.payload.length >= 2); - this.sceneCount = this.payload[0]; - if (this.version >= 3) { - this.supportsSlowRefresh = !!(this.payload[1] & 0b1000_0000); + super(options); + + // TODO: Check implementation: + this.sceneCount = options.sceneCount; + this.supportsSlowRefresh = options.supportsSlowRefresh; + for ( + const [scene, keys] of Object.entries( + options.supportedKeyAttributes, + ) + ) { + this._supportedKeyAttributes.set( + parseInt(scene), + keys, + ); } - const bitMaskBytes = (this.payload[1] & 0b110) >>> 1; - const identicalKeyAttributes = !!(this.payload[1] & 0b1); - const numEntries = identicalKeyAttributes ? 1 : this.sceneCount; + } - validatePayload(this.payload.length >= 2 + bitMaskBytes * numEntries); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): CentralSceneCCSupportedReport { + validatePayload(raw.payload.length >= 2); + const sceneCount = raw.payload[0]; + const supportsSlowRefresh: MaybeNotKnown = + !!(raw.payload[1] & 0b1000_0000); + const bitMaskBytes = (raw.payload[1] & 0b110) >>> 1; + const identicalKeyAttributes = !!(raw.payload[1] & 0b1); + const numEntries = identicalKeyAttributes ? 1 : sceneCount; + validatePayload(raw.payload.length >= 2 + bitMaskBytes * numEntries); + const supportedKeyAttributes: Record< + number, + readonly CentralSceneKeys[] + > = {}; for (let i = 0; i < numEntries; i++) { - const mask = this.payload.subarray( + const mask = raw.payload.subarray( 2 + i * bitMaskBytes, 2 + (i + 1) * bitMaskBytes, ); - this._supportedKeyAttributes.set( - i + 1, - parseBitMask(mask, CentralSceneKeys.KeyPressed), + supportedKeyAttributes[i + 1] = parseBitMask( + mask, + CentralSceneKeys.KeyPressed, ); } + if (identicalKeyAttributes) { // The key attributes are only transmitted for scene 1, copy them to the others - for (let i = 2; i <= this.sceneCount; i++) { - this._supportedKeyAttributes.set( - i, - this._supportedKeyAttributes.get(1)!, - ); + for (let i = 2; i <= sceneCount; i++) { + supportedKeyAttributes[i] = supportedKeyAttributes[1]; } } + + return new CentralSceneCCSupportedReport({ + nodeId: ctx.sourceNodeId, + sceneCount, + supportsSlowRefresh, + supportedKeyAttributes, + }); } - 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 +492,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 +505,7 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -456,24 +515,41 @@ export class CentralSceneCCSupportedReport extends CentralSceneCC { @expectedCCResponse(CentralSceneCCSupportedReport) export class CentralSceneCCSupportedGet extends CentralSceneCC {} +// @publicAPI +export interface CentralSceneCCConfigurationReportOptions { + slowRefresh: boolean; +} + @CCCommand(CentralSceneCommand.ConfigurationReport) export class CentralSceneCCConfigurationReport extends CentralSceneCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); - this.slowRefresh = !!(this.payload[0] & 0b1000_0000); + // TODO: Check implementation: + this.slowRefresh = options.slowRefresh; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): CentralSceneCCConfigurationReport { + validatePayload(raw.payload.length >= 1); + const slowRefresh = !!(raw.payload[0] & 0b1000_0000); + + return new CentralSceneCCConfigurationReport({ + nodeId: ctx.sourceNodeId, + slowRefresh, + }); } @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 }, }; } @@ -484,9 +560,7 @@ export class CentralSceneCCConfigurationReport extends CentralSceneCC { export class CentralSceneCCConfigurationGet extends CentralSceneCC {} // @publicAPI -export interface CentralSceneCCConfigurationSetOptions - extends CCCommandOptions -{ +export interface CentralSceneCCConfigurationSetOptions { slowRefresh: boolean; } @@ -494,32 +568,36 @@ export interface CentralSceneCCConfigurationSetOptions @useSupervision() export class CentralSceneCCConfigurationSet extends CentralSceneCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | CentralSceneCCConfigurationSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.slowRefresh = options.slowRefresh; - } + super(options); + this.slowRefresh = options.slowRefresh; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): CentralSceneCCConfigurationSet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new CentralSceneCCConfigurationSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..02a3bf8b2c87 100644 --- a/packages/cc/src/cc/ClimateControlScheduleCC.ts +++ b/packages/cc/src/cc/ClimateControlScheduleCC.ts @@ -4,22 +4,22 @@ import { type MessageOrCCLogEntry, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { + CCEncodingContext, + CCParsingContext, + 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"; import { CCAPI } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, overrideType: type, overrideState: state, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -214,7 +214,7 @@ export class ClimateControlScheduleCC extends CommandClass { } // @publicAPI -export interface ClimateControlScheduleCCSetOptions extends CCCommandOptions { +export interface ClimateControlScheduleCCSetOptions { weekday: Weekday; switchPoints: Switchpoint[]; } @@ -223,27 +223,31 @@ export interface ClimateControlScheduleCCSetOptions extends CCCommandOptions { @useSupervision() export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ClimateControlScheduleCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.switchPoints = options.switchPoints; - this.weekday = options.weekday; - } + super(options); + this.switchPoints = options.switchPoints; + this.weekday = options.weekday; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ClimateControlScheduleCCSet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ClimateControlScheduleCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 +261,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: `${ @@ -284,23 +288,46 @@ export class ClimateControlScheduleCCSet extends ClimateControlScheduleCC { } } +// @publicAPI +export interface ClimateControlScheduleCCReportOptions { + weekday: Weekday; + schedule: Switchpoint[]; +} + @CCCommand(ClimateControlScheduleCommand.Report) export class ClimateControlScheduleCCReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 28); // 1 + 9 * 3 - this.weekday = this.payload[0] & 0b111; + // TODO: Check implementation: + this.weekday = options.weekday; + this.schedule = options.schedule; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ClimateControlScheduleCCReport { + validatePayload(raw.payload.length >= 28); + const weekday: Weekday = raw.payload[0] & 0b111; const allSwitchpoints: Switchpoint[] = []; for (let i = 0; i <= 8; i++) { allSwitchpoints.push( - decodeSwitchpoint(this.payload.subarray(1 + 3 * i)), + decodeSwitchpoint(raw.payload.subarray(1 + 3 * i)), ); } - this.schedule = allSwitchpoints.filter((sp) => sp.state !== "Unused"); + + const schedule: Switchpoint[] = allSwitchpoints.filter((sp) => + sp.state !== "Unused" + ); + + return new ClimateControlScheduleCCReport({ + nodeId: ctx.sourceNodeId, + weekday, + schedule, + }); } public readonly weekday: Weekday; @@ -311,9 +338,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: `${ @@ -336,7 +363,7 @@ export class ClimateControlScheduleCCReport extends ClimateControlScheduleCC { } // @publicAPI -export interface ClimateControlScheduleCCGetOptions extends CCCommandOptions { +export interface ClimateControlScheduleCCGetOptions { weekday: Weekday; } @@ -344,56 +371,77 @@ export interface ClimateControlScheduleCCGetOptions extends CCCommandOptions { @expectedCCResponse(ClimateControlScheduleCCReport) export class ClimateControlScheduleCCGet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ClimateControlScheduleCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.weekday = options.weekday; - } + super(options); + this.weekday = options.weekday; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ClimateControlScheduleCCGet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ClimateControlScheduleCCGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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) }, }; } } +// @publicAPI +export interface ClimateControlScheduleCCChangedReportOptions { + changeCounter: number; +} + @CCCommand(ClimateControlScheduleCommand.ChangedReport) export class ClimateControlScheduleCCChangedReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.changeCounter = options.changeCounter; + } - validatePayload(this.payload.length >= 1); - this.changeCounter = this.payload[0]; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ClimateControlScheduleCCChangedReport { + validatePayload(raw.payload.length >= 1); + const changeCounter = raw.payload[0]; + + return new ClimateControlScheduleCCChangedReport({ + nodeId: ctx.sourceNodeId, + changeCounter, + }); } 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 }, }; } @@ -405,20 +453,40 @@ export class ClimateControlScheduleCCChangedGet extends ClimateControlScheduleCC {} +// @publicAPI +export interface ClimateControlScheduleCCOverrideReportOptions { + overrideType: ScheduleOverrideType; + overrideState: SetbackState; +} + @CCCommand(ClimateControlScheduleCommand.OverrideReport) export class ClimateControlScheduleCCOverrideReport extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.overrideType = options.overrideType; + this.overrideState = options.overrideState; + } - validatePayload(this.payload.length >= 2); - this.overrideType = this.payload[0] & 0b11; - this.overrideState = decodeSetbackState(this.payload[1]) - || this.payload[1]; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ClimateControlScheduleCCOverrideReport { + validatePayload(raw.payload.length >= 2); + const overrideType: ScheduleOverrideType = raw.payload[0] & 0b11; + const overrideState: SetbackState = decodeSetbackState(raw.payload[1]) + || raw.payload[1]; + + return new ClimateControlScheduleCCOverrideReport({ + nodeId: ctx.sourceNodeId, + overrideType, + overrideState, + }); } @ccValue(ClimateControlScheduleCCValues.overrideType) @@ -427,9 +495,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, @@ -448,9 +516,7 @@ export class ClimateControlScheduleCCOverrideGet {} // @publicAPI -export interface ClimateControlScheduleCCOverrideSetOptions - extends CCCommandOptions -{ +export interface ClimateControlScheduleCCOverrideSetOptions { overrideType: ScheduleOverrideType; overrideState: SetbackState; } @@ -461,37 +527,41 @@ export class ClimateControlScheduleCCOverrideSet extends ClimateControlScheduleCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ClimateControlScheduleCCOverrideSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.overrideType = options.overrideType; - this.overrideState = options.overrideState; - } + super(options); + this.overrideType = options.overrideType; + this.overrideState = options.overrideState; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ClimateControlScheduleCCOverrideSet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ClimateControlScheduleCCOverrideSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..ada36ace818d 100644 --- a/packages/cc/src/cc/ClockCC.ts +++ b/packages/cc/src/cc/ClockCC.ts @@ -1,6 +1,7 @@ import type { MessageOrCCLogEntry, SupervisionResult, + WithAddress, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -11,19 +12,19 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -53,11 +54,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -74,14 +75,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, + endpointIndex: 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 +91,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 +134,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", }); @@ -138,7 +143,7 @@ export class ClockCC extends CommandClass { } // @publicAPI -export interface ClockCCSetOptions extends CCCommandOptions { +export interface ClockCCSetOptions { weekday: Weekday; hour: number; minute: number; @@ -148,38 +153,41 @@ export interface ClockCCSetOptions extends CCCommandOptions { @useSupervision() export class ClockCCSet extends ClockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ClockCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.weekday = options.weekday; - this.hour = options.hour; - this.minute = options.minute; - } + super(options); + this.weekday = options.weekday; + this.hour = options.hour; + this.minute = options.minute; + } + + public static from(_raw: CCRaw, _ctx: CCParsingContext): ClockCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ClockCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } public weekday: Weekday; 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( @@ -198,32 +206,52 @@ export class ClockCCSet extends ClockCC { } } +// @publicAPI +export interface ClockCCReportOptions { + weekday: Weekday; + hour: number; + minute: number; +} + @CCCommand(ClockCommand.Report) export class ClockCCReport extends ClockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 2); + super(options); + + // TODO: Check implementation: + this.weekday = options.weekday; + this.hour = options.hour; + this.minute = options.minute; + } - this.weekday = this.payload[0] >>> 5; - this.hour = this.payload[0] & 0b11111; - this.minute = this.payload[1]; + public static from(raw: CCRaw, ctx: CCParsingContext): ClockCCReport { + validatePayload(raw.payload.length >= 2); + const weekday: Weekday = raw.payload[0] >>> 5; + const hour = raw.payload[0] & 0b11111; + const minute = raw.payload[1]; validatePayload( - this.weekday <= Weekday.Sunday, - this.hour <= 23, - this.minute <= 59, + weekday <= Weekday.Sunday, + hour <= 23, + minute <= 59, ); + + return new ClockCCReport({ + nodeId: ctx.sourceNodeId, + weekday, + hour, + minute, + }); } public readonly weekday: Weekday; 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..feaeb43a1cfe 100644 --- a/packages/cc/src/cc/ColorSwitchCC.ts +++ b/packages/cc/src/cc/ColorSwitchCC.ts @@ -8,6 +8,7 @@ import { type ValueDB, type ValueID, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, isUnsupervisedOrSucceeded, @@ -17,12 +18,11 @@ import { } from "@zwave-js/core"; import { type MaybeNotKnown, encodeBitMask } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { - type AllOrNone, getEnumMemberName, isEnumMember, keysOf, @@ -43,10 +43,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -207,11 +209,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ColorSwitchCCSupportedReport >( cc, @@ -225,12 +227,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, + endpointIndex: this.endpoint.index, colorComponent: component, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -245,13 +247,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, + endpointIndex: 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 +272,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 +365,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -381,13 +383,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, + endpointIndex: 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 +545,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 +580,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 +594,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 +613,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 +653,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 +663,7 @@ export class ColorSwitchCC extends CommandClass { } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -668,7 +674,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,41 +686,45 @@ export interface ColorSwitchCCSupportedReportOptions { @CCCommand(ColorSwitchCommand.SupportedReport) export class ColorSwitchCCSupportedReport extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (ColorSwitchCCSupportedReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - // Docs say 'variable length', but the table shows 2 bytes. - validatePayload(this.payload.length >= 2); + this.supportedColorComponents = options.supportedColorComponents; + } - this.supportedColorComponents = parseBitMask( - this.payload.subarray(0, 2), - ColorComponent["Warm White"], - ); - } else { - this.supportedColorComponents = options.supportedColorComponents; - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ColorSwitchCCSupportedReport { + // Docs say 'variable length', but the table shows 2 bytes. + validatePayload(raw.payload.length >= 2); + const supportedColorComponents: ColorComponent[] = parseBitMask( + raw.payload.subarray(0, 2), + ColorComponent["Warm White"], + ); + + return new ColorSwitchCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedColorComponents, + }); } @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)}`) @@ -729,56 +739,60 @@ export class ColorSwitchCCSupportedReport extends ColorSwitchCC { export class ColorSwitchCCSupportedGet extends ColorSwitchCC {} // @publicAPI -export type ColorSwitchCCReportOptions = - & { - colorComponent: ColorComponent; - currentValue: number; - } - & AllOrNone<{ - targetValue: number; - duration: Duration | string; - }>; +export interface ColorSwitchCCReportOptions { + colorComponent: ColorComponent; + currentValue: number; + targetValue?: number; + duration?: Duration | string; +} @CCCommand(ColorSwitchCommand.Report) export class ColorSwitchCCReport extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (ColorSwitchCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.colorComponent = this.payload[0]; - this.currentValue = this.payload[1]; + this.colorComponent = options.colorComponent; + this.currentValue = options.currentValue; + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } - if (this.version >= 3 && this.payload.length >= 4) { - this.targetValue = this.payload[2]; - this.duration = Duration.parseReport(this.payload[3]); - } - } else { - this.colorComponent = options.colorComponent; - this.currentValue = options.currentValue; - this.targetValue = options.targetValue; - this.duration = Duration.from(options.duration); + public static from(raw: CCRaw, ctx: CCParsingContext): ColorSwitchCCReport { + validatePayload(raw.payload.length >= 2); + const colorComponent: ColorComponent = raw.payload[0]; + const currentValue = raw.payload[1]; + let targetValue: number | undefined; + let duration: Duration | undefined; + + if (raw.payload.length >= 4) { + targetValue = raw.payload[2]; + duration = Duration.parseReport(raw.payload[3]); } + + return new ColorSwitchCCReport({ + nodeId: ctx.sourceNodeId, + colorComponent, + currentValue, + targetValue, + duration, + }); } - 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 +802,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 +814,7 @@ export class ColorSwitchCCReport extends ColorSwitchCC { // Update collective hex value if required const supportsHex = !!this.getValue( - applHost, + ctx, ColorSwitchCCValues.supportsHexColor, ); if ( @@ -811,7 +825,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 +833,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 +858,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 +872,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,14 +890,14 @@ export class ColorSwitchCCReport extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface ColorSwitchCCGetOptions extends CCCommandOptions { +export interface ColorSwitchCCGetOptions { colorComponent: ColorComponent; } @@ -898,16 +912,20 @@ function testResponseForColorSwitchGet( @expectedCCResponse(ColorSwitchCCReport, testResponseForColorSwitchGet) export class ColorSwitchCCGet extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ColorSwitchCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this._colorComponent = this.payload[0]; - } else { - this._colorComponent = options.colorComponent; - } + super(options); + this._colorComponent = options.colorComponent; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ColorSwitchCCGet { + validatePayload(raw.payload.length >= 1); + const colorComponent: ColorComponent = raw.payload[0]; + + return new ColorSwitchCCGet({ + nodeId: ctx.sourceNodeId, + colorComponent, + }); } private _colorComponent: ColorComponent; @@ -924,14 +942,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,56 +969,61 @@ export type ColorSwitchCCSetOptions = (ColorTable | { hexColor: string }) & { @useSupervision() export class ColorSwitchCCSet extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & ColorSwitchCCSetOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const populatedColorCount = this.payload[0] & 0b11111; - - validatePayload(this.payload.length >= 1 + populatedColorCount * 2); - this.colorTable = {}; - let offset = 1; - for (let color = 0; color < populatedColorCount; color++) { - const component = this.payload[offset]; - const value = this.payload[offset + 1]; - const key = colorComponentToTableKey(component); - // @ts-expect-error - if (key) this.colorTable[key] = value; - offset += 2; - } - if (this.payload.length > offset) { - this.duration = Duration.parseSet(this.payload[offset]); + super(options); + // Populate properties from options object + if ("hexColor" in options) { + const match = hexColorRegex.exec(options.hexColor); + if (!match) { + throw new ZWaveError( + `${options.hexColor} is not a valid HEX color string`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.colorTable = { + red: parseInt(match.groups!.red, 16), + green: parseInt(match.groups!.green, 16), + blue: parseInt(match.groups!.blue, 16), + }; } else { - // Populate properties from options object - if ("hexColor" in options) { - const match = hexColorRegex.exec(options.hexColor); - if (!match) { - throw new ZWaveError( - `${options.hexColor} is not a valid HEX color string`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.colorTable = { - red: parseInt(match.groups!.red, 16), - green: parseInt(match.groups!.green, 16), - blue: parseInt(match.groups!.blue, 16), - }; - } else { - this.colorTable = pick(options, colorTableKeys as any[]); - } - this.duration = Duration.from(options.duration); + this.colorTable = pick(options, colorTableKeys as any[]); + } + this.duration = Duration.from(options.duration); + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ColorSwitchCCSet { + validatePayload(raw.payload.length >= 1); + const populatedColorCount = raw.payload[0] & 0b11111; + + validatePayload(raw.payload.length >= 1 + populatedColorCount * 2); + const colorTable: ColorTable = {}; + let offset = 1; + for (let color = 0; color < populatedColorCount; color++) { + const component = raw.payload[offset]; + const value = raw.payload[offset + 1]; + const key = colorComponentToTableKey(component); + // @ts-expect-error + if (key) this.colorTable[key] = value; + offset += 2; + } + + let duration: Duration | undefined; + if (raw.payload.length > offset) { + duration = Duration.parseSet(raw.payload[offset]); } + + return new ColorSwitchCCSet({ + nodeId: ctx.sourceNodeId, + ...colorTable, + duration, + }); } 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 +1040,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 +1050,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 +1065,7 @@ export class ColorSwitchCCSet extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1072,32 +1096,40 @@ export type ColorSwitchCCStartLevelChangeOptions = @useSupervision() export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & ColorSwitchCCStartLevelChangeOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5; - this.ignoreStartLevel = !!ignoreStartLevel; - const direction = (this.payload[0] & 0b0_1_0_00000) >>> 6; - this.direction = direction ? "down" : "up"; - - this.colorComponent = this.payload[1]; - this.startLevel = this.payload[2]; - - if (this.payload.length >= 4) { - this.duration = Duration.parseSet(this.payload[3]); - } - } else { - this.duration = Duration.from(options.duration); - this.ignoreStartLevel = options.ignoreStartLevel; - this.startLevel = options.startLevel ?? 0; - this.direction = options.direction; - this.colorComponent = options.colorComponent; + super(options); + this.duration = Duration.from(options.duration); + this.ignoreStartLevel = options.ignoreStartLevel; + this.startLevel = options.startLevel ?? 0; + this.direction = options.direction; + this.colorComponent = options.colorComponent; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ColorSwitchCCStartLevelChange { + validatePayload(raw.payload.length >= 3); + const ignoreStartLevel = !!((raw.payload[0] & 0b0_0_1_00000) >>> 5); + const direction = ((raw.payload[0] & 0b0_1_0_00000) >>> 6) + ? "down" + : "up"; + const colorComponent: ColorComponent = raw.payload[1]; + const startLevel = raw.payload[2]; + let duration: Duration | undefined; + if (raw.payload.length >= 4) { + duration = Duration.parseSet(raw.payload[3]); } + + return new ColorSwitchCCStartLevelChange({ + nodeId: ctx.sourceNodeId, + ignoreStartLevel, + direction, + colorComponent, + startLevel, + duration, + }); } public duration: Duration | undefined; @@ -1106,7 +1138,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 +1148,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 +1158,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,14 +1176,14 @@ export class ColorSwitchCCStartLevelChange extends ColorSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface ColorSwitchCCStopLevelChangeOptions extends CCCommandOptions { +export interface ColorSwitchCCStopLevelChangeOptions { colorComponent: ColorComponent; } @@ -1158,30 +1191,35 @@ export interface ColorSwitchCCStopLevelChangeOptions extends CCCommandOptions { @useSupervision() export class ColorSwitchCCStopLevelChange extends ColorSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ColorSwitchCCStopLevelChangeOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.colorComponent = this.payload[0]; - } else { - this.colorComponent = options.colorComponent; - } + super(options); + this.colorComponent = options.colorComponent; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ColorSwitchCCStopLevelChange { + validatePayload(raw.payload.length >= 1); + const colorComponent: ColorComponent = raw.payload[0]; + + return new ColorSwitchCCStopLevelChange({ + nodeId: ctx.sourceNodeId, + colorComponent, + }); } 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..c10cc6811adf 100644 --- a/packages/cc/src/cc/ConfigurationCC.ts +++ b/packages/cc/src/cc/ConfigurationCC.ts @@ -3,16 +3,20 @@ 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, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodePartial, @@ -27,9 +31,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 +45,7 @@ import { composeObject } from "alcalzone-shared/objects"; import { padStart } from "alcalzone-shared/strings"; import { CCAPI, + type CCAPIEndpoint, POLL_VALUE, type PollValueImplementation, SET_VALUE, @@ -47,10 +55,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -144,26 +154,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 +198,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 +220,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 +239,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 +285,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 +346,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 +369,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 +392,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 +531,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, + endpointIndex: this.endpoint.index, parameter, allowUnexpectedResponse, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -547,7 +548,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 +558,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 +600,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, + endpointIndex: this.endpoint.index, parameters: distinctParameters, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCBulkReport >( cc, @@ -619,12 +620,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCReport >( cc, @@ -638,12 +639,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,26 +676,23 @@ 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, + endpointIndex: this.endpoint.index, resetToDefault: false, parameter: normalized.parameter, value, @@ -702,7 +700,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 +713,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,9 +734,9 @@ 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, + endpointIndex: this.endpoint.index, parameters: allParams.map((v) => v.parameter), valueSize: allParams[0].valueSize, valueFormat: allParams[0].valueFormat, @@ -746,7 +744,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,16 +782,16 @@ 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, + endpointIndex: this.endpoint.index, parameter, value, valueSize, valueFormat, }); supervisionResults.push( - await this.applHost.sendCommand(cc, this.commandOptions), + await this.host.sendCommand(cc, this.commandOptions), ); } return mergeSupervisionResults(supervisionResults); @@ -814,18 +812,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, + endpointIndex: this.endpoint.index, parameter, resetToDefault: true, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -841,13 +850,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, + endpointIndex: 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,15 +864,15 @@ 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, + endpointIndex: this.endpoint.index, parameter, resetToDefault: true, }), ); for (const cc of CCs) { - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } } @@ -878,11 +887,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, + endpointIndex: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -891,12 +900,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCPropertiesReport >( cc, @@ -924,12 +933,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCNameReport >( cc, @@ -944,12 +953,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ConfigurationCCInfoReport >( cc, @@ -981,18 +990,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 +1014,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 +1046,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 +1095,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 +1104,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 +1114,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 +1126,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 +1183,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 +1200,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 +1238,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 +1247,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 +1264,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 +1274,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 +1305,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 +1342,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 +1350,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 +1365,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; Object.assign(metadata, info); // And store it back this.setMetadata( - applHost, + ctx, ConfigurationCCValues.paramInformation(parameter, valueBitMask), metadata, ); @@ -1366,13 +1376,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 +1395,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 +1420,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 +1437,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 +1451,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 +1485,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 +1508,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; // Allow overwriting the param info (mark it as unloaded) this.setValue( - applHost, + ctx, ConfigurationCCValues.isParamInformationFromConfig, false, ); @@ -1527,7 +1543,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; isFromConfig: true, }); this.extendParamInformation( - applHost, + ctx, param.parameter, param.valueBitMask, paramInfo, @@ -1536,14 +1552,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 +1571,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 +1585,7 @@ alters capabilities: ${!!properties.altersCapabilities}`; && (propertyKey == undefined || typeof propertyKey === "number") ) { const paramInfo = this.getParamInformation( - applHost, + ctx, property, propertyKey, ); @@ -1581,12 +1597,12 @@ alters capabilities: ${!!properties.altersCapabilities}`; } return ret; } - return super.translateProperty(applHost, property, propertyKey); + return super.translateProperty(ctx, property, propertyKey); } } /** @publicAPI */ -export interface ConfigurationCCReportOptions extends CCCommandOptions { +export interface ConfigurationCCReportOptions { parameter: number; value: ConfigValue; valueSize: number; @@ -1596,37 +1612,45 @@ export interface ConfigurationCCReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.Report) export class ConfigurationCCReport extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // All fields must be present - validatePayload(this.payload.length > 2); - this.parameter = this.payload[0]; - this.valueSize = this.payload[1] & 0b111; - // Ensure we received a valid report - validatePayload( - this.valueSize >= 1, - this.valueSize <= 4, - this.payload.length >= 2 + this.valueSize, - ); - // Default to parsing the value as SignedInteger, like the specs say. - // We try to re-interpret the value in persistValues() - this.value = parseValue( - this.payload.subarray(2), - this.valueSize, - ConfigValueFormat.SignedInteger, - ); - } else { - this.parameter = options.parameter; - this.value = options.value; - this.valueSize = options.valueSize; - this.valueFormat = options.valueFormat; - } + super(options); + + this.parameter = options.parameter; + this.value = options.value; + this.valueSize = options.valueSize; + this.valueFormat = options.valueFormat; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCReport { + // All fields must be present + validatePayload(raw.payload.length > 2); + const parameter = raw.payload[0]; + const valueSize = raw.payload[1] & 0b111; + + // Ensure we received a valid report + validatePayload( + valueSize >= 1, + valueSize <= 4, + raw.payload.length >= 2 + valueSize, + ); + // Default to parsing the value as SignedInteger, like the specs say. + // We try to re-interpret the value in persistValues() + const value = parseValue( + raw.payload.subarray(2), + valueSize, + ConfigValueFormat.SignedInteger, + ); + + return new ConfigurationCCReport({ + nodeId: ctx.sourceNodeId, + parameter, + valueSize, + value, + }); } public parameter: number; @@ -1634,14 +1658,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 +1679,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 +1699,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 +1726,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 +1745,7 @@ export class ConfigurationCCReport extends ConfigurationCC { } else { // This is a single param this.setValue( - applHost, + ctx, ConfigurationCCValues.paramInformation(this.parameter), this.value, ); @@ -1727,7 +1753,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 +1766,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, @@ -1766,7 +1792,7 @@ function testResponseForConfigurationGet( } // @publicAPI -export interface ConfigurationCCGetOptions extends CCCommandOptions { +export interface ConfigurationCCGetOptions { parameter: number; /** * If this is `true`, responses with different parameters than expected are accepted @@ -1779,32 +1805,35 @@ export interface ConfigurationCCGetOptions extends CCCommandOptions { @expectedCCResponse(ConfigurationCCReport, testResponseForConfigurationGet) export class ConfigurationCCGet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.parameter = this.payload[0]; - this.allowUnexpectedResponse = false; - } else { - this.parameter = options.parameter; - this.allowUnexpectedResponse = options.allowUnexpectedResponse - ?? false; - } + super(options); + this.parameter = options.parameter; + this.allowUnexpectedResponse = options.allowUnexpectedResponse + ?? false; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ConfigurationCCGet { + validatePayload(raw.payload.length >= 1); + const parameter = raw.payload[0]; + + return new ConfigurationCCGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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 }, }; } @@ -1812,78 +1841,72 @@ export class ConfigurationCCGet extends ConfigurationCC { // @publicAPI export type ConfigurationCCSetOptions = - & CCCommandOptions - & ( - | { - parameter: number; - resetToDefault: true; - } - | { - parameter: number; - resetToDefault?: false; - valueSize: number; - /** How the value is encoded. Defaults to SignedInteger */ - valueFormat?: ConfigValueFormat; - value: ConfigValue; - } - ); + | { + parameter: number; + resetToDefault: true; + } + | { + parameter: number; + resetToDefault?: false; + valueSize: number; + /** How the value is encoded. Defaults to SignedInteger */ + valueFormat?: ConfigValueFormat; + value: ConfigValue; + }; @CCCommand(ConfigurationCommand.Set) @useSupervision() export class ConfigurationCCSet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ConfigurationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.parameter = this.payload[0]; - this.resetToDefault = !!(this.payload[1] & 0b1000_0000); - this.valueSize = this.payload[1] & 0b111; - - // Ensure we received a valid report - validatePayload( - this.valueSize >= 1, - this.valueSize <= 4, - this.payload.length >= 2 + this.valueSize, - ); - // Parse the value as signed integer. We don't know the format here. - this.value = parseValue( - this.payload.subarray(2), - this.valueSize, - ConfigValueFormat.SignedInteger, - ); - } 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 - this.valueSize = options.valueSize; - this.valueFormat = options.valueFormat - ?? ConfigValueFormat.SignedInteger; - this.value = options.value; - } + super(options); + this.parameter = options.parameter; + this.resetToDefault = !!options.resetToDefault; + if (!options.resetToDefault) { + // TODO: Default to the stored value size + this.valueSize = options.valueSize; + this.valueFormat = options.valueFormat + ?? ConfigValueFormat.SignedInteger; + this.value = options.value; } } + public static from(raw: CCRaw, ctx: CCParsingContext): ConfigurationCCSet { + validatePayload(raw.payload.length >= 2); + const parameter = raw.payload[0]; + const resetToDefault = !!(raw.payload[1] & 0b1000_0000); + const valueSize: number | undefined = raw.payload[1] & 0b111; + + // Ensure we received a valid report + validatePayload( + valueSize >= 1, + valueSize <= 4, + raw.payload.length >= 2 + valueSize, + ); + // Parse the value as signed integer. We don't know the format here. + const value: number | undefined = parseValue( + raw.payload.subarray(2), + valueSize, + ConfigValueFormat.SignedInteger, + ); + + return new ConfigurationCCSet({ + nodeId: ctx.sourceNodeId, + parameter, + resetToDefault, + valueSize, + value, + }); + } + public resetToDefault: boolean; public parameter: number; public valueSize: number | undefined; 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 +1946,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 +1967,7 @@ export class ConfigurationCCSet extends ConfigurationCC { message.value = configValueToString(this.value); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1952,7 +1975,6 @@ export class ConfigurationCCSet extends ConfigurationCC { // @publicAPI export type ConfigurationCCBulkSetOptions = - & CCCommandOptions & { parameters: number[]; handshake?: boolean; @@ -1978,46 +2000,50 @@ function getResponseForBulkSet(cc: ConfigurationCCBulkSet) { @useSupervision() export class ConfigurationCCBulkSet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCBulkSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload + super(options); + this._parameters = options.parameters; + if (this._parameters.length < 1) { + throw new ZWaveError( + `In a ConfigurationCC.BulkSet, parameters must be a non-empty array`, + ZWaveErrorCodes.CC_Invalid, + ); + } else if (!isConsecutiveArray(this._parameters)) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `A ConfigurationCC.BulkSet can only be used for consecutive parameters`, + ZWaveErrorCodes.CC_Invalid, ); + } + this._handshake = !!options.handshake; + this._resetToDefault = !!options.resetToDefault; + if (!!options.resetToDefault) { + this._valueSize = 1; + this._valueFormat = ConfigValueFormat.SignedInteger; + this._values = this._parameters.map(() => 0); } else { - this._parameters = options.parameters; - if (this._parameters.length < 1) { - throw new ZWaveError( - `In a ConfigurationCC.BulkSet, parameters must be a non-empty array`, - ZWaveErrorCodes.CC_Invalid, - ); - } else if (!isConsecutiveArray(this._parameters)) { - throw new ZWaveError( - `A ConfigurationCC.BulkSet can only be used for consecutive parameters`, - ZWaveErrorCodes.CC_Invalid, - ); - } - this._handshake = !!options.handshake; - this._resetToDefault = !!options.resetToDefault; - if (!!options.resetToDefault) { - this._valueSize = 1; - this._valueFormat = ConfigValueFormat.SignedInteger; - this._values = this._parameters.map(() => 0); - } else { - this._valueSize = options.valueSize; - this._valueFormat = options.valueFormat - ?? ConfigValueFormat.SignedInteger; - this._values = options.values; - } + this._valueSize = options.valueSize; + this._valueFormat = options.valueFormat + ?? ConfigValueFormat.SignedInteger; + this._values = options.values; } } + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ConfigurationCCBulkSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ConfigurationCCBulkSet({ + // nodeId: ctx.sourceNodeId, + // }); + } + private _parameters: number[]; public get parameters(): number[] { return this._parameters; @@ -2043,7 +2069,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 +2113,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,55 +2135,83 @@ export class ConfigurationCCBulkSet extends ConfigurationCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface ConfigurationCCBulkReportOptions { + reportsToFollow: number; + defaultValues: boolean; + isHandshakeResponse: boolean; + valueSize: number; + values: Record; +} + @CCCommand(ConfigurationCommand.BulkReport) export class ConfigurationCCBulkReport extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - // Ensure we received enough bytes for the preamble - validatePayload(this.payload.length >= 5); - const firstParameter = this.payload.readUInt16BE(0); - const numParams = this.payload[2]; - this._reportsToFollow = this.payload[3]; - this._defaultValues = !!(this.payload[4] & 0b1000_0000); - this._isHandshakeResponse = !!(this.payload[4] & 0b0100_0000); - this._valueSize = this.payload[4] & 0b111; + // TODO: Check implementation: + this.reportsToFollow = options.reportsToFollow; + this.defaultValues = options.defaultValues; + this.isHandshakeResponse = options.isHandshakeResponse; + this.valueSize = options.valueSize; + for (const [param, value] of Object.entries(options.values)) { + this._values.set(parseInt(param), value); + } + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCBulkReport { + // Ensure we received enough bytes for the preamble + validatePayload(raw.payload.length >= 5); + const firstParameter = raw.payload.readUInt16BE(0); + const numParams = raw.payload[2]; + const reportsToFollow = raw.payload[3]; + const defaultValues = !!(raw.payload[4] & 0b1000_0000); + const isHandshakeResponse = !!(raw.payload[4] & 0b0100_0000); + const valueSize = raw.payload[4] & 0b111; // Ensure the payload is long enough for all reported values - validatePayload(this.payload.length >= 5 + numParams * this._valueSize); + validatePayload(raw.payload.length >= 5 + numParams * valueSize); + const values: Record = {}; for (let i = 0; i < numParams; i++) { const param = firstParameter + i; - this._values.set( - param, - // Default to parsing the value as SignedInteger, like the specs say. - // We try to re-interpret the value in persistValues() - parseValue( - this.payload.subarray(5 + i * this.valueSize), - this.valueSize, - ConfigValueFormat.SignedInteger, - ), + // Default to parsing the value as SignedInteger, like the specs say. + // We try to re-interpret the value in persistValues() + values[param] = parseValue( + raw.payload.subarray(5 + i * valueSize), + valueSize, + ConfigValueFormat.SignedInteger, ); } + + return new ConfigurationCCBulkReport({ + nodeId: ctx.sourceNodeId, + reportsToFollow, + defaultValues, + isHandshakeResponse, + valueSize, + values, + }); } - 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 ( @@ -2168,14 +2222,14 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { // Re-interpret the value with the new format value = reInterpretSignedValue( value, - this._valueSize, + this.valueSize, oldParamInformation.format, ); this._values.set(parameter, value); } this.setValue( - applHost, + ctx, ConfigurationCCValues.paramInformation(parameter), value, ); @@ -2184,9 +2238,14 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { return true; } - private _reportsToFollow: number; - public get reportsToFollow(): number { - return this._reportsToFollow; + public reportsToFollow: number; + public defaultValues: boolean; + public isHandshakeResponse: boolean; + public valueSize: number; + + private _values = new Map(); + public get values(): ReadonlyMap { + return this._values; } public getPartialCCSessionId(): Record | undefined { @@ -2195,34 +2254,14 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { } public expectMoreMessages(): boolean { - return this._reportsToFollow > 0; - } - - private _defaultValues: boolean; - public get defaultValues(): boolean { - return this._defaultValues; - } - - private _isHandshakeResponse: boolean; - public get isHandshakeResponse(): boolean { - return this._isHandshakeResponse; - } - - private _valueSize: number; - public get valueSize(): number { - return this._valueSize; - } - - private _values = new Map(); - public get values(): ReadonlyMap { - return this._values; + return this.reportsToFollow > 0; } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { - "handshake response": this._isHandshakeResponse, - "default values": this._defaultValues, - "value size": this._valueSize, + "handshake response": this.isHandshakeResponse, + "default values": this.defaultValues, + "value size": this.valueSize, "reports to follow": this.reportsToFollow, }; if (this._values.size > 0) { @@ -2234,14 +2273,14 @@ export class ConfigurationCCBulkReport extends ConfigurationCC { .join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface ConfigurationCCBulkGetOptions extends CCCommandOptions { +export interface ConfigurationCCBulkGetOptions { parameters: number[]; } @@ -2249,51 +2288,55 @@ export interface ConfigurationCCBulkGetOptions extends CCCommandOptions { @expectedCCResponse(ConfigurationCCBulkReport) export class ConfigurationCCBulkGet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCBulkGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload + super(options); + this._parameters = options.parameters.sort(); + if (!isConsecutiveArray(this.parameters)) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `A ConfigurationCC.BulkGet can only be used for consecutive parameters`, + ZWaveErrorCodes.CC_Invalid, ); - } else { - this._parameters = options.parameters.sort(); - if (!isConsecutiveArray(this.parameters)) { - throw new ZWaveError( - `A ConfigurationCC.BulkGet can only be used for consecutive parameters`, - ZWaveErrorCodes.CC_Invalid, - ); - } } } + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ConfigurationCCBulkGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ConfigurationCCBulkGet({ + // nodeId: ctx.sourceNodeId, + // }); + } + private _parameters: number[]; public get parameters(): number[] { 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(", ") }, }; } } /** @publicAPI */ -export interface ConfigurationCCNameReportOptions extends CCCommandOptions { +export interface ConfigurationCCNameReportOptions { parameter: number; name: string; reportsToFollow: number; @@ -2302,47 +2345,55 @@ export interface ConfigurationCCNameReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.NameReport) export class ConfigurationCCNameReport extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCNameReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // Parameter and # of reports must be present - validatePayload(this.payload.length >= 3); - this.parameter = this.payload.readUInt16BE(0); - this.reportsToFollow = this.payload[2]; - if (this.reportsToFollow > 0) { - // If more reports follow, the info must at least be one byte - validatePayload(this.payload.length >= 4); - } - this.name = this.payload.subarray(3).toString("utf8"); - } else { - this.parameter = options.parameter; - this.name = options.name; - this.reportsToFollow = options.reportsToFollow; + super(options); + + this.parameter = options.parameter; + this.name = options.name; + this.reportsToFollow = options.reportsToFollow; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCNameReport { + // Parameter and # of reports must be present + validatePayload(raw.payload.length >= 3); + const parameter = raw.payload.readUInt16BE(0); + const reportsToFollow = raw.payload[2]; + + if (reportsToFollow > 0) { + // If more reports follow, the info must at least be one byte + validatePayload(raw.payload.length >= 4); } + const name: string = raw.payload.subarray(3).toString("utf8"); + + return new ConfigurationCCNameReport({ + nodeId: ctx.sourceNodeId, + parameter, + reportsToFollow, + name, + }); } public readonly parameter: number; 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 +2408,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 +2417,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 +2437,8 @@ export class ConfigurationCCNameReport extends ConfigurationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: ConfigurationCCNameReport[], + _ctx: CCParsingContext, ): void { // Concat the name this.name = [...partials, this] @@ -2395,9 +2446,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,36 +2462,43 @@ export class ConfigurationCCNameReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCNameReport) export class ConfigurationCCNameGet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.parameter = this.payload.readUInt16BE(0); - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCNameGet { + validatePayload(raw.payload.length >= 2); + const parameter = raw.payload.readUInt16BE(0); + + return new ConfigurationCCNameGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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 }, }; } } /** @publicAPI */ -export interface ConfigurationCCInfoReportOptions extends CCCommandOptions { +export interface ConfigurationCCInfoReportOptions { parameter: number; info: string; reportsToFollow: number; @@ -2449,43 +2507,51 @@ export interface ConfigurationCCInfoReportOptions extends CCCommandOptions { @CCCommand(ConfigurationCommand.InfoReport) export class ConfigurationCCInfoReport extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCInfoReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // Parameter and # of reports must be present - validatePayload(this.payload.length >= 3); - this.parameter = this.payload.readUInt16BE(0); - this.reportsToFollow = this.payload[2]; - if (this.reportsToFollow > 0) { - // If more reports follow, the info must at least be one byte - validatePayload(this.payload.length >= 4); - } - this.info = this.payload.subarray(3).toString("utf8"); - } else { - this.parameter = options.parameter; - this.info = options.info; - this.reportsToFollow = options.reportsToFollow; + super(options); + + this.parameter = options.parameter; + this.info = options.info; + this.reportsToFollow = options.reportsToFollow; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCInfoReport { + // Parameter and # of reports must be present + validatePayload(raw.payload.length >= 3); + const parameter = raw.payload.readUInt16BE(0); + const reportsToFollow = raw.payload[2]; + + if (reportsToFollow > 0) { + // If more reports follow, the info must at least be one byte + validatePayload(raw.payload.length >= 4); } + const info: string = raw.payload.subarray(3).toString("utf8"); + + return new ConfigurationCCInfoReport({ + nodeId: ctx.sourceNodeId, + parameter, + reportsToFollow, + info, + }); } public readonly parameter: number; 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 +2560,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 +2575,7 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { ? `Refer to ${firstParamLabel}` : this.info; - this.extendParamInformation(applHost, paramNumber, bitMask, { + this.extendParamInformation(ctx, paramNumber, bitMask, { description, }); @@ -2517,7 +2583,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 +2592,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 +2612,8 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { } public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: ConfigurationCCInfoReport[], + _ctx: CCParsingContext, ): void { // Concat the info this.info = [...partials, this] @@ -2555,9 +2621,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,38 +2637,43 @@ export class ConfigurationCCInfoReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCInfoReport) export class ConfigurationCCInfoGet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.parameter = this.payload.readUInt16BE(0); - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCInfoGet { + validatePayload(raw.payload.length >= 2); + const parameter = raw.payload.readUInt16BE(0); + + return new ConfigurationCCInfoGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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 }, }; } } /** @publicAPI */ -export interface ConfigurationCCPropertiesReportOptions - extends CCCommandOptions -{ +export interface ConfigurationCCPropertiesReportOptions { parameter: number; valueSize: number; valueFormat: ConfigValueFormat; @@ -2619,105 +2690,133 @@ export interface ConfigurationCCPropertiesReportOptions @CCCommand(ConfigurationCommand.PropertiesReport) export class ConfigurationCCPropertiesReport extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ConfigurationCCPropertiesReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.parameter = this.payload.readUInt16BE(0); - this.valueFormat = (this.payload[2] & 0b111000) >>> 3; - this.valueSize = this.payload[2] & 0b111; - - // GH#1309 Some devices don't tell us the first parameter if we query #0 - // Instead, they contain 0x000000 - if (this.valueSize === 0 && this.payload.length < 5) { - this.nextParameter = 0; - return; - } - - // Ensure the payload contains the two bytes for next parameter - const nextParameterOffset = 3 + 3 * this.valueSize; - validatePayload(this.payload.length >= nextParameterOffset + 2); + super(options); - if (this.valueSize > 0) { - if (this.valueFormat === ConfigValueFormat.BitField) { - this.minValue = 0; - } else { - this.minValue = parseValue( - this.payload.subarray(3), - this.valueSize, - this.valueFormat, - ); - } - this.maxValue = parseValue( - this.payload.subarray(3 + this.valueSize), - this.valueSize, - this.valueFormat, + this.parameter = options.parameter; + this.valueSize = options.valueSize; + this.valueFormat = options.valueFormat; + if (this.valueSize > 0) { + if (options.minValue == undefined) { + throw new ZWaveError( + "The minimum value must be set when the value size is non-zero", + ZWaveErrorCodes.Argument_Invalid, ); - this.defaultValue = parseValue( - this.payload.subarray(3 + 2 * this.valueSize), - this.valueSize, - this.valueFormat, + } else if (options.maxValue == undefined) { + throw new ZWaveError( + "The maximum value must be set when the value size is non-zero", + ZWaveErrorCodes.Argument_Invalid, ); - } - 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 if (options.defaultValue == undefined) { + throw new ZWaveError( + "The default value must be set when the value size is non-zero", + ZWaveErrorCodes.Argument_Invalid, ); + } + this.minValue = options.minValue; + this.maxValue = options.maxValue; + this.defaultValue = options.defaultValue; + } + this.nextParameter = options.nextParameter; + this.altersCapabilities = options.altersCapabilities; + this.isReadonly = options.isReadonly; + this.isAdvanced = options.isAdvanced; + this.noBulkSupport = options.noBulkSupport; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCPropertiesReport { + validatePayload(raw.payload.length >= 3); + const parameter = raw.payload.readUInt16BE(0); + const valueFormat: ConfigValueFormat = (raw.payload[2] & 0b111000) + >>> 3; + const valueSize = raw.payload[2] & 0b111; + + // GH#1309 Some devices don't tell us the first parameter if we query #0 + // Instead, they contain 0x000000 + let nextParameter; + + if (valueSize === 0 && raw.payload.length < 5) { + nextParameter = 0; + return new ConfigurationCCPropertiesReport({ + nodeId: ctx.sourceNodeId, + parameter, + valueFormat, + valueSize, + nextParameter, + }); + } + + // Ensure the payload contains the two bytes for next parameter + const nextParameterOffset = 3 + 3 * valueSize; + validatePayload(raw.payload.length >= nextParameterOffset + 2); + + let minValue: MaybeNotKnown; + let maxValue: MaybeNotKnown; + let defaultValue: MaybeNotKnown; + + if (valueSize > 0) { + if (valueFormat === ConfigValueFormat.BitField) { + minValue = 0; } else { - this.nextParameter = this.payload.readUInt16BE( - nextParameterOffset, + minValue = parseValue( + raw.payload.subarray(3), + valueSize, + valueFormat, ); - - // Ensure the payload contains a byte for the 2nd option flags - validatePayload(this.payload.length >= nextParameterOffset + 3); - const options1 = this.payload[2]; - const options2 = this.payload[3 + 3 * this.valueSize + 2]; - this.altersCapabilities = !!(options1 & 0b1000_0000); - this.isReadonly = !!(options1 & 0b0100_0000); - this.isAdvanced = !!(options2 & 0b1); - this.noBulkSupport = !!(options2 & 0b10); } - } else { - this.parameter = options.parameter; - this.valueSize = options.valueSize; - this.valueFormat = options.valueFormat; - if (this.valueSize > 0) { - if (options.minValue == undefined) { - throw new ZWaveError( - "The minimum value must be set when the value size is non-zero", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (options.maxValue == undefined) { - throw new ZWaveError( - "The maximum value must be set when the value size is non-zero", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (options.defaultValue == undefined) { - throw new ZWaveError( - "The default value must be set when the value size is non-zero", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.minValue = options.minValue; - this.maxValue = options.maxValue; - this.defaultValue = options.defaultValue; - } - this.nextParameter = options.nextParameter; - this.altersCapabilities = options.altersCapabilities; - this.isReadonly = options.isReadonly; - this.isAdvanced = options.isAdvanced; - this.noBulkSupport = options.noBulkSupport; + maxValue = parseValue( + raw.payload.subarray(3 + valueSize), + valueSize, + valueFormat, + ); + defaultValue = parseValue( + raw.payload.subarray(3 + 2 * valueSize), + valueSize, + valueFormat, + ); } + + nextParameter = raw.payload.readUInt16BE( + nextParameterOffset, + ); + + let altersCapabilities: MaybeNotKnown; + let isReadonly: MaybeNotKnown; + let isAdvanced: MaybeNotKnown; + let noBulkSupport: MaybeNotKnown; + + if (raw.payload.length >= nextParameterOffset + 3) { + // V4 adds an options byte after the next parameter and two bits in byte 2 + const options1 = raw.payload[2]; + const options2 = raw.payload[3 + 3 * valueSize + 2]; + altersCapabilities = !!(options1 & 0b1000_0000); + isReadonly = !!(options1 & 0b0100_0000); + isAdvanced = !!(options2 & 0b1); + noBulkSupport = !!(options2 & 0b10); + } + + return new ConfigurationCCPropertiesReport({ + nodeId: ctx.sourceNodeId, + parameter, + valueFormat, + valueSize, + nextParameter, + minValue, + maxValue, + defaultValue, + altersCapabilities, + isReadonly, + isAdvanced, + noBulkSupport, + }); } - 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 +2835,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 +2846,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { ); this.extendParamInformation( - applHost, + ctx, this.parameter, undefined, paramInfo, @@ -2763,7 +2862,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 +2877,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { ); this.extendParamInformation( - applHost, + ctx, this.parameter, mask, paramInfo, @@ -2807,7 +2906,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 +2954,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 +2989,7 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { message["bulk support"] = !this.noBulkSupport; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2900,29 +2999,36 @@ export class ConfigurationCCPropertiesReport extends ConfigurationCC { @expectedCCResponse(ConfigurationCCPropertiesReport) export class ConfigurationCCPropertiesGet extends ConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.parameter = this.payload.readUInt16BE(0); - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ConfigurationCCPropertiesGet { + validatePayload(raw.payload.length >= 2); + const parameter = raw.payload.readUInt16BE(0); + + return new ConfigurationCCPropertiesGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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..88f330e12af7 100644 --- a/packages/cc/src/cc/DeviceResetLocallyCC.ts +++ b/packages/cc/src/cc/DeviceResetLocallyCC.ts @@ -4,13 +4,9 @@ import { TransmitOptions, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost } from "@zwave-js/host/safe"; +import { type CCParsingContext } from "@zwave-js/host"; import { CCAPI } from "../lib/API"; -import { - CommandClass, - type CommandClassOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -39,15 +35,15 @@ 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, + endpointIndex: this.endpoint.index, }); 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,16 +69,18 @@ export class DeviceResetLocallyCC extends CommandClass { @CCCommand(DeviceResetLocallyCommand.Notification) export class DeviceResetLocallyCCNotification extends DeviceResetLocallyCC { - public constructor(host: ZWaveHost, options: CommandClassOptions) { - super(host, options); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DeviceResetLocallyCCNotification { + // We need to make sure this doesn't get parsed accidentally, e.g. because of a bit flip - if (gotDeserializationOptions(options)) { - // We need to make sure this doesn't get parsed accidentally, e.g. because of a bit flip + // This CC has no payload + validatePayload(raw.payload.length === 0); + // The driver ensures before handling it that it is only received from the root device - // This CC has no payload - validatePayload(this.payload.length === 0); - // It MUST be issued by the root device - validatePayload(this.endpointIndex === 0); - } + return new DeviceResetLocallyCCNotification({ + nodeId: ctx.sourceNodeId, + }); } } diff --git a/packages/cc/src/cc/DoorLockCC.ts b/packages/cc/src/cc/DoorLockCC.ts index 58e393219bd2..e39fa35a7b8d 100644 --- a/packages/cc/src/cc/DoorLockCC.ts +++ b/packages/cc/src/cc/DoorLockCC.ts @@ -1,13 +1,14 @@ import { CommandClasses, Duration, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -16,9 +17,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -34,10 +35,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -253,10 +255,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 +266,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 +277,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 +288,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 +299,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 +310,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 +321,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 +465,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCCapabilitiesReport >( cc, @@ -497,11 +499,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCOperationReport >( cc, @@ -531,12 +533,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, + endpointIndex: this.endpoint.index, mode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -548,12 +550,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, + endpointIndex: 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 +565,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockCCConfigurationReport >( cc, @@ -594,18 +596,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 +624,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 +657,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 +669,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 +678,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 +693,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 +766,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 +774,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 +811,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", @@ -815,7 +821,7 @@ latch status: ${status.latchStatus}`; } // @publicAPI -export interface DoorLockCCOperationSetOptions extends CCCommandOptions { +export interface DoorLockCCOperationSetOptions { mode: DoorLockMode; } @@ -823,39 +829,43 @@ export interface DoorLockCCOperationSetOptions extends CCCommandOptions { @useSupervision() export class DoorLockCCOperationSet extends DoorLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | DoorLockCCOperationSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload + super(options); + if (options.mode === DoorLockMode.Unknown) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `Unknown is not a valid door lock target state!`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.mode === DoorLockMode.Unknown) { - throw new ZWaveError( - `Unknown is not a valid door lock target state!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.mode = options.mode; } + this.mode = options.mode; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): DoorLockCCOperationSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new DoorLockCCOperationSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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), }, @@ -863,79 +873,128 @@ export class DoorLockCCOperationSet extends DoorLockCC { } } +// @publicAPI +export interface DoorLockCCOperationReportOptions { + currentMode: DoorLockMode; + outsideHandlesCanOpenDoor: DoorHandleStatus; + insideHandlesCanOpenDoor: DoorHandleStatus; + doorStatus?: "closed" | "open"; + boltStatus?: "unlocked" | "locked"; + latchStatus?: "closed" | "open"; + lockTimeout?: number; + targetMode?: DoorLockMode; + duration?: Duration; +} + @CCCommand(DoorLockCommand.OperationReport) export class DoorLockCCOperationReport extends DoorLockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 5); - - this.currentMode = this.payload[0]; - this.outsideHandlesCanOpenDoor = [ - !!(this.payload[1] & 0b0001_0000), - !!(this.payload[1] & 0b0010_0000), - !!(this.payload[1] & 0b0100_0000), - !!(this.payload[1] & 0b1000_0000), + super(options); + + // TODO: Check implementation: + this.currentMode = options.currentMode; + this.outsideHandlesCanOpenDoor = options.outsideHandlesCanOpenDoor; + this.insideHandlesCanOpenDoor = options.insideHandlesCanOpenDoor; + this.doorStatus = options.doorStatus; + this.boltStatus = options.boltStatus; + this.latchStatus = options.latchStatus; + this.lockTimeout = options.lockTimeout; + this.targetMode = options.targetMode; + this.duration = options.duration; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DoorLockCCOperationReport { + validatePayload(raw.payload.length >= 5); + const currentMode: DoorLockMode = raw.payload[0]; + const outsideHandlesCanOpenDoor: DoorHandleStatus = [ + !!(raw.payload[1] & 0b0001_0000), + !!(raw.payload[1] & 0b0010_0000), + !!(raw.payload[1] & 0b0100_0000), + !!(raw.payload[1] & 0b1000_0000), ]; - this.insideHandlesCanOpenDoor = [ - !!(this.payload[1] & 0b0001), - !!(this.payload[1] & 0b0010), - !!(this.payload[1] & 0b0100), - !!(this.payload[1] & 0b1000), + const insideHandlesCanOpenDoor: DoorHandleStatus = [ + !!(raw.payload[1] & 0b0001), + !!(raw.payload[1] & 0b0010), + !!(raw.payload[1] & 0b0100), + !!(raw.payload[1] & 0b1000), ]; - - this.doorStatus = !!(this.payload[2] & 0b1) ? "closed" : "open"; - this.boltStatus = !!(this.payload[2] & 0b10) ? "unlocked" : "locked"; - this.latchStatus = !!(this.payload[2] & 0b100) ? "closed" : "open"; - + const doorStatus: "closed" | "open" | undefined = + !!(raw.payload[2] & 0b1) + ? "closed" + : "open"; + const boltStatus: "unlocked" | "locked" | undefined = + !!(raw.payload[2] & 0b10) ? "unlocked" : "locked"; + const latchStatus: "closed" | "open" | undefined = + !!(raw.payload[2] & 0b100) + ? "closed" + : "open"; // Ignore invalid timeout values - const lockTimeoutMinutes = this.payload[3]; - const lockTimeoutSeconds = this.payload[4]; + const lockTimeoutMinutes = raw.payload[3]; + const lockTimeoutSeconds = raw.payload[4]; + let lockTimeout: number | undefined; if (lockTimeoutMinutes <= 253 && lockTimeoutSeconds <= 59) { - this.lockTimeout = lockTimeoutSeconds + lockTimeoutMinutes * 60; + lockTimeout = lockTimeoutSeconds + lockTimeoutMinutes * 60; } - if (this.version >= 3 && this.payload.length >= 7) { - this.targetMode = this.payload[5]; - this.duration = Duration.parseReport(this.payload[6]); + let targetMode: DoorLockMode | undefined; + let duration: Duration | undefined; + if (raw.payload.length >= 7) { + targetMode = raw.payload[5]; + duration = Duration.parseReport(raw.payload[6]); } + + return new DoorLockCCOperationReport({ + nodeId: ctx.sourceNodeId, + currentMode, + outsideHandlesCanOpenDoor, + insideHandlesCanOpenDoor, + doorStatus, + boltStatus, + latchStatus, + lockTimeout, + targetMode, + duration, + }); } - 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 +1025,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 +1055,7 @@ export class DoorLockCCOperationReport extends DoorLockCC { message["lock timeout"] = `${this.lockTimeout} seconds`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1006,44 +1065,90 @@ export class DoorLockCCOperationReport extends DoorLockCC { @expectedCCResponse(DoorLockCCOperationReport) export class DoorLockCCOperationGet extends DoorLockCC {} +// @publicAPI +export interface DoorLockCCConfigurationReportOptions { + operationType: DoorLockOperationType; + outsideHandlesCanOpenDoorConfiguration: DoorHandleStatus; + insideHandlesCanOpenDoorConfiguration: DoorHandleStatus; + lockTimeoutConfiguration?: number; + autoRelockTime?: number; + holdAndReleaseTime?: number; + twistAssist?: boolean; + blockToBlock?: boolean; +} + @CCCommand(DoorLockCommand.ConfigurationReport) export class DoorLockCCConfigurationReport extends DoorLockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 4); - - this.operationType = this.payload[0]; - this.outsideHandlesCanOpenDoorConfiguration = [ - !!(this.payload[1] & 0b0001_0000), - !!(this.payload[1] & 0b0010_0000), - !!(this.payload[1] & 0b0100_0000), - !!(this.payload[1] & 0b1000_0000), + super(options); + + // TODO: Check implementation: + this.operationType = options.operationType; + this.outsideHandlesCanOpenDoorConfiguration = + options.outsideHandlesCanOpenDoorConfiguration; + this.insideHandlesCanOpenDoorConfiguration = + options.insideHandlesCanOpenDoorConfiguration; + this.lockTimeoutConfiguration = options.lockTimeoutConfiguration; + this.autoRelockTime = options.autoRelockTime; + this.holdAndReleaseTime = options.holdAndReleaseTime; + this.twistAssist = options.twistAssist; + this.blockToBlock = options.blockToBlock; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DoorLockCCConfigurationReport { + validatePayload(raw.payload.length >= 4); + const operationType: DoorLockOperationType = raw.payload[0]; + const outsideHandlesCanOpenDoorConfiguration: DoorHandleStatus = [ + !!(raw.payload[1] & 0b0001_0000), + !!(raw.payload[1] & 0b0010_0000), + !!(raw.payload[1] & 0b0100_0000), + !!(raw.payload[1] & 0b1000_0000), ]; - this.insideHandlesCanOpenDoorConfiguration = [ - !!(this.payload[1] & 0b0001), - !!(this.payload[1] & 0b0010), - !!(this.payload[1] & 0b0100), - !!(this.payload[1] & 0b1000), + const insideHandlesCanOpenDoorConfiguration: DoorHandleStatus = [ + !!(raw.payload[1] & 0b0001), + !!(raw.payload[1] & 0b0010), + !!(raw.payload[1] & 0b0100), + !!(raw.payload[1] & 0b1000), ]; - if (this.operationType === DoorLockOperationType.Timed) { - const lockTimeoutMinutes = this.payload[2]; - const lockTimeoutSeconds = this.payload[3]; + let lockTimeoutConfiguration: number | undefined; + if (operationType === DoorLockOperationType.Timed) { + const lockTimeoutMinutes = raw.payload[2]; + const lockTimeoutSeconds = raw.payload[3]; if (lockTimeoutMinutes <= 0xfd && lockTimeoutSeconds <= 59) { - this.lockTimeoutConfiguration = lockTimeoutSeconds + lockTimeoutConfiguration = lockTimeoutSeconds + lockTimeoutMinutes * 60; } } - if (this.version >= 4 && this.payload.length >= 5) { - this.autoRelockTime = this.payload.readUInt16BE(4); - this.holdAndReleaseTime = this.payload.readUInt16BE(6); - const flags = this.payload[8]; - this.twistAssist = !!(flags & 0b1); - this.blockToBlock = !!(flags & 0b10); + let autoRelockTime: number | undefined; + let holdAndReleaseTime: number | undefined; + let twistAssist: boolean | undefined; + let blockToBlock: boolean | undefined; + if (raw.payload.length >= 5) { + autoRelockTime = raw.payload.readUInt16BE(4); + holdAndReleaseTime = raw.payload.readUInt16BE(6); + + const flags = raw.payload[8]; + twistAssist = !!(flags & 0b1); + blockToBlock = !!(flags & 0b10); } + + return new DoorLockCCConfigurationReport({ + nodeId: ctx.sourceNodeId, + operationType, + outsideHandlesCanOpenDoorConfiguration, + insideHandlesCanOpenDoorConfiguration, + lockTimeoutConfiguration, + autoRelockTime, + holdAndReleaseTime, + twistAssist, + blockToBlock, + }); } @ccValue(DoorLockCCValues.operationType) @@ -1065,50 +1170,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 +1222,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 +1253,7 @@ export class DoorLockCCConfigurationReport extends DoorLockCC { message["block-to-block enabled"] = this.blockToBlock; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1184,30 +1289,34 @@ export type DoorLockCCConfigurationSetOptions = @useSupervision() export class DoorLockCCConfigurationSet extends DoorLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & DoorLockCCConfigurationSetOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.operationType = options.operationType; - this.outsideHandlesCanOpenDoorConfiguration = - options.outsideHandlesCanOpenDoorConfiguration; - this.insideHandlesCanOpenDoorConfiguration = - options.insideHandlesCanOpenDoorConfiguration; - this.lockTimeoutConfiguration = options.lockTimeoutConfiguration; - this.autoRelockTime = options.autoRelockTime; - this.holdAndReleaseTime = options.holdAndReleaseTime; - this.twistAssist = options.twistAssist; - this.blockToBlock = options.blockToBlock; - } + super(options); + this.operationType = options.operationType; + this.outsideHandlesCanOpenDoorConfiguration = + options.outsideHandlesCanOpenDoorConfiguration; + this.insideHandlesCanOpenDoorConfiguration = + options.insideHandlesCanOpenDoorConfiguration; + this.lockTimeoutConfiguration = options.lockTimeoutConfiguration; + this.autoRelockTime = options.autoRelockTime; + this.holdAndReleaseTime = options.holdAndReleaseTime; + this.twistAssist = options.twistAssist; + this.blockToBlock = options.blockToBlock; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): DoorLockCCConfigurationSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new DoorLockCCConfigurationSet({ + // nodeId: ctx.sourceNodeId, + // }); } public operationType: DoorLockOperationType; @@ -1219,7 +1328,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 +1378,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,62 +1420,105 @@ export class DoorLockCCConfigurationSet extends DoorLockCC { message["enable block-to-block"] = this.blockToBlock; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface DoorLockCCCapabilitiesReportOptions { + supportedOperationTypes: DoorLockOperationType[]; + supportedDoorLockModes: DoorLockMode[]; + supportedOutsideHandles: DoorHandleStatus; + supportedInsideHandles: DoorHandleStatus; + doorSupported: boolean; + boltSupported: boolean; + latchSupported: boolean; + blockToBlockSupported: boolean; + twistAssistSupported: boolean; + holdAndReleaseSupported: boolean; + autoRelockSupported: boolean; +} + @CCCommand(DoorLockCommand.CapabilitiesReport) export class DoorLockCCCapabilitiesReport extends DoorLockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.supportedOperationTypes = options.supportedOperationTypes; + this.supportedDoorLockModes = options.supportedDoorLockModes; + this.supportedOutsideHandles = options.supportedOutsideHandles; + this.supportedInsideHandles = options.supportedInsideHandles; + this.doorSupported = options.doorSupported; + this.boltSupported = options.boltSupported; + this.latchSupported = options.latchSupported; + this.blockToBlockSupported = options.blockToBlockSupported; + this.twistAssistSupported = options.twistAssistSupported; + this.holdAndReleaseSupported = options.holdAndReleaseSupported; + this.autoRelockSupported = options.autoRelockSupported; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DoorLockCCCapabilitiesReport { // parse variable length operation type bit mask - validatePayload(this.payload.length >= 1); - const bitMaskLength = this.payload[0] & 0b11111; + validatePayload(raw.payload.length >= 1); + const bitMaskLength = raw.payload[0] & 0b11111; let offset = 1; - validatePayload(this.payload.length >= offset + bitMaskLength + 1); - this.supportedOperationTypes = parseBitMask( - this.payload.subarray(offset, offset + bitMaskLength), + validatePayload(raw.payload.length >= offset + bitMaskLength + 1); + const supportedOperationTypes: DoorLockOperationType[] = parseBitMask( + raw.payload.subarray(offset, offset + bitMaskLength), // bit 0 is reserved, bitmask starts at 1 0, ); offset += bitMaskLength; - // parse variable length door lock mode list - const listLength = this.payload[offset]; + const listLength = raw.payload[offset]; offset += 1; - validatePayload(this.payload.length >= offset + listLength + 3); - this.supportedDoorLockModes = [ - ...this.payload.subarray(offset, offset + listLength), + validatePayload(raw.payload.length >= offset + listLength + 3); + const supportedDoorLockModes: DoorLockMode[] = [ + ...raw.payload.subarray(offset, offset + listLength), ]; offset += listLength; - - this.supportedOutsideHandles = [ - !!(this.payload[offset] & 0b0001_0000), - !!(this.payload[offset] & 0b0010_0000), - !!(this.payload[offset] & 0b0100_0000), - !!(this.payload[offset] & 0b1000_0000), + const supportedOutsideHandles: DoorHandleStatus = [ + !!(raw.payload[offset] & 0b0001_0000), + !!(raw.payload[offset] & 0b0010_0000), + !!(raw.payload[offset] & 0b0100_0000), + !!(raw.payload[offset] & 0b1000_0000), ]; - this.supportedInsideHandles = [ - !!(this.payload[offset] & 0b0001), - !!(this.payload[offset] & 0b0010), - !!(this.payload[offset] & 0b0100), - !!(this.payload[offset] & 0b1000), + const supportedInsideHandles: DoorHandleStatus = [ + !!(raw.payload[offset] & 0b0001), + !!(raw.payload[offset] & 0b0010), + !!(raw.payload[offset] & 0b0100), + !!(raw.payload[offset] & 0b1000), ]; - - this.doorSupported = !!(this.payload[offset + 1] & 0b1); - this.boltSupported = !!(this.payload[offset + 1] & 0b10); - this.latchSupported = !!(this.payload[offset + 1] & 0b100); - - this.blockToBlockSupported = !!(this.payload[offset + 2] & 0b1); - this.twistAssistSupported = !!(this.payload[offset + 2] & 0b10); - this.holdAndReleaseSupported = !!(this.payload[offset + 2] & 0b100); - this.autoRelockSupported = !!(this.payload[offset + 2] & 0b1000); + const doorSupported = !!(raw.payload[offset + 1] & 0b1); + const boltSupported = !!(raw.payload[offset + 1] & 0b10); + const latchSupported = !!(raw.payload[offset + 1] & 0b100); + const blockToBlockSupported = !!(raw.payload[offset + 2] & 0b1); + const twistAssistSupported = !!(raw.payload[offset + 2] & 0b10); + const holdAndReleaseSupported = !!(raw.payload[offset + 2] & 0b100); + const autoRelockSupported = !!(raw.payload[offset + 2] & 0b1000); + + return new DoorLockCCCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + supportedOperationTypes, + supportedDoorLockModes, + supportedOutsideHandles, + supportedInsideHandles, + doorSupported, + boltSupported, + latchSupported, + blockToBlockSupported, + twistAssistSupported, + holdAndReleaseSupported, + autoRelockSupported, + }); } public readonly supportedOperationTypes: readonly DoorLockOperationType[]; @@ -1396,9 +1548,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..7c839b95278d 100644 --- a/packages/cc/src/cc/DoorLockLoggingCC.ts +++ b/packages/cc/src/cc/DoorLockLoggingCC.ts @@ -4,23 +4,24 @@ import { type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type WithAddress, ZWaveError, ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -126,11 +127,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockLoggingCCRecordsSupportedReport >( cc, @@ -149,12 +150,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, + endpointIndex: this.endpoint.index, recordNumber, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< DoorLockLoggingCCRecordReport >( cc, @@ -170,33 +171,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 +209,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 +221,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", @@ -224,24 +229,41 @@ export class DoorLockLoggingCC extends CommandClass { } } +// @publicAPI +export interface DoorLockLoggingCCRecordsSupportedReportOptions { + recordsCount: number; +} + @CCCommand(DoorLockLoggingCommand.RecordsSupportedReport) export class DoorLockLoggingCCRecordsSupportedReport extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); + super(options); - this.recordsCount = this.payload[0]; + // TODO: Check implementation: + this.recordsCount = options.recordsCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DoorLockLoggingCCRecordsSupportedReport { + validatePayload(raw.payload.length >= 1); + const recordsCount = raw.payload[0]; + + return new DoorLockLoggingCCRecordsSupportedReport({ + nodeId: ctx.sourceNodeId, + recordsCount, + }); } @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, }, @@ -260,38 +282,51 @@ function eventTypeToLabel(eventType: DoorLockLoggingEventType): string { @expectedCCResponse(DoorLockLoggingCCRecordsSupportedReport) export class DoorLockLoggingCCRecordsSupportedGet extends DoorLockLoggingCC {} +// @publicAPI +export interface DoorLockLoggingCCRecordReportOptions { + recordNumber: number; + record?: DoorLockLoggingRecord; +} + @CCCommand(DoorLockLoggingCommand.RecordReport) export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 11); + super(options); - this.recordNumber = this.payload[0]; - const recordStatus = this.payload[5] >>> 5; - if (recordStatus === DoorLockLoggingRecordStatus.Empty) { - return; - } else { + // TODO: Check implementation: + this.recordNumber = options.recordNumber; + this.record = options.record; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): DoorLockLoggingCCRecordReport { + validatePayload(raw.payload.length >= 11); + const recordNumber = raw.payload[0]; + const recordStatus = raw.payload[5] >>> 5; + let record: DoorLockLoggingRecord | undefined; + if (recordStatus !== DoorLockLoggingRecordStatus.Empty) { const dateSegments = { - year: this.payload.readUInt16BE(1), - month: this.payload[3], - day: this.payload[4], - hour: this.payload[5] & 0b11111, - minute: this.payload[6], - second: this.payload[7], + year: raw.payload.readUInt16BE(1), + month: raw.payload[3], + day: raw.payload[4], + hour: raw.payload[5] & 0b11111, + minute: raw.payload[6], + second: raw.payload[7], }; - const eventType = this.payload[8]; - const recordUserID = this.payload[9]; - const userCodeLength = this.payload[10]; + const eventType = raw.payload[8]; + const recordUserID = raw.payload[9]; + const userCodeLength = raw.payload[10]; validatePayload( userCodeLength <= 10, - this.payload.length >= 11 + userCodeLength, + raw.payload.length >= 11 + userCodeLength, ); - const userCodeBuffer = this.payload.subarray( + const userCodeBuffer = raw.payload.subarray( 11, 11 + userCodeLength, ); @@ -302,7 +337,7 @@ export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { ? userCodeString : userCodeBuffer; - this.record = { + record = { eventType: eventType, label: eventTypeToLabel(eventType), timestamp: segmentsToDate(dateSegments).toISOString(), @@ -310,12 +345,18 @@ export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { userCode, }; } + + return new DoorLockLoggingCCRecordReport({ + nodeId: ctx.sourceNodeId, + recordNumber, + record, + }); } 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,14 +379,14 @@ export class DoorLockLoggingCCRecordReport extends DoorLockLoggingCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface DoorLockLoggingCCRecordGetOptions extends CCCommandOptions { +export interface DoorLockLoggingCCRecordGetOptions { recordNumber: number; } @@ -366,32 +407,36 @@ function testResponseForDoorLockLoggingRecordGet( ) export class DoorLockLoggingCCRecordGet extends DoorLockLoggingCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | DoorLockLoggingCCRecordGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.recordNumber = options.recordNumber; - } + super(options); + this.recordNumber = options.recordNumber; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): DoorLockLoggingCCRecordGet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new DoorLockLoggingCCRecordGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..a45eb28c93a0 100644 --- a/packages/cc/src/cc/EnergyProductionCC.ts +++ b/packages/cc/src/cc/EnergyProductionCC.ts @@ -3,15 +3,16 @@ import { type MessageOrCCLogEntry, MessagePriority, ValueMetadata, + type WithAddress, encodeFloatWithScale, parseFloatWithScale, validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host"; import { getEnumMemberName, pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; @@ -22,10 +23,11 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -109,12 +111,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EnergyProductionCCReport >( cc, @@ -132,28 +134,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 +173,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( @@ -184,7 +190,7 @@ export class EnergyProductionCC extends CommandClass { } // @publicAPI -export interface EnergyProductionCCReportOptions extends CCCommandOptions { +export interface EnergyProductionCCReportOptions { parameter: EnergyProductionParameter; scale: EnergyProductionScale; value: number; @@ -193,36 +199,45 @@ export interface EnergyProductionCCReportOptions extends CCCommandOptions { @CCCommand(EnergyProductionCommand.Report) export class EnergyProductionCCReport extends EnergyProductionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | EnergyProductionCCReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.parameter = this.payload[0]; - const { value, scale } = parseFloatWithScale( - this.payload.subarray(1), - ); - this.value = value; - this.scale = getEnergyProductionScale(this.parameter, scale); - } else { - this.parameter = options.parameter; - this.value = options.value; - this.scale = options.scale; - } + super(options); + this.parameter = options.parameter; + this.value = options.value; + this.scale = options.scale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EnergyProductionCCReport { + validatePayload(raw.payload.length >= 2); + const parameter: EnergyProductionParameter = raw.payload[0]; + const { value, scale: rawScale } = parseFloatWithScale( + raw.payload.subarray(1), + ); + const scale: EnergyProductionScale = getEnergyProductionScale( + parameter, + rawScale, + ); + + return new EnergyProductionCCReport({ + nodeId: ctx.sourceNodeId, + parameter, + value, + scale, + }); } public readonly parameter: EnergyProductionParameter; 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 +245,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( @@ -259,7 +274,7 @@ export class EnergyProductionCCReport extends EnergyProductionCC { } // @publicAPI -export interface EnergyProductionCCGetOptions extends CCCommandOptions { +export interface EnergyProductionCCGetOptions { parameter: EnergyProductionParameter; } @@ -277,30 +292,35 @@ function testResponseForEnergyProductionGet( ) export class EnergyProductionCCGet extends EnergyProductionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | EnergyProductionCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.parameter = this.payload[0]; - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EnergyProductionCCGet { + validatePayload(raw.payload.length >= 1); + const parameter: EnergyProductionParameter = raw.payload[0]; + + return new EnergyProductionCCGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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..b7d70a3a7091 100644 --- a/packages/cc/src/cc/EntryControlCC.ts +++ b/packages/cc/src/cc/EntryControlCC.ts @@ -6,6 +6,7 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, getCCName, @@ -14,9 +15,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { buffer2hex, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -30,10 +31,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -113,11 +115,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCKeySupportedReport >( cc, @@ -133,11 +135,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCEventSupportedReport >( cc, @@ -162,11 +164,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< EntryControlCCConfigurationReport >( cc, @@ -187,13 +189,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, + endpointIndex: 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 +271,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 +294,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 +311,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 +319,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 +327,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 +335,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 +356,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 +383,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} @@ -388,36 +394,54 @@ key cache timeout: ${conf.keyCacheTimeout} seconds`, } } +// @publicAPI +export interface EntryControlCCNotificationOptions { + sequenceNumber: number; + dataType: EntryControlDataTypes; + eventType: EntryControlEventTypes; + eventData?: string | Buffer; +} + @CCCommand(EntryControlCommand.Notification) export class EntryControlCCNotification extends EntryControlCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 4); - this.sequenceNumber = this.payload[0]; - this.dataType = this.payload[1] & 0b11; - this.eventType = this.payload[2]; - const eventDataLength = this.payload[3]; - validatePayload(eventDataLength >= 0 && eventDataLength <= 32); + // TODO: Check implementation: + this.sequenceNumber = options.sequenceNumber; + this.dataType = options.dataType; + this.eventType = options.eventType; + this.eventData = options.eventData; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EntryControlCCNotification { + validatePayload(raw.payload.length >= 4); + const sequenceNumber = raw.payload[0]; + let dataType: EntryControlDataTypes = raw.payload[1] & 0b11; + const eventType: EntryControlEventTypes = raw.payload[2]; + const eventDataLength = raw.payload[3]; + validatePayload(eventDataLength >= 0 && eventDataLength <= 32); const offset = 4; - validatePayload(this.payload.length >= offset + eventDataLength); + validatePayload(raw.payload.length >= offset + eventDataLength); + let eventData: string | Buffer | undefined; if (eventDataLength > 0) { // We shouldn't need to check this, since the specs are pretty clear which format to expect. // 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?.( - this.nodeId as number, + const noStrictValidation = !!ctx.getDeviceConfig?.( + ctx.sourceNodeId, )?.compat?.disableStrictEntryControlDataValidation; - const eventData = Buffer.from( - this.payload.subarray(offset, offset + eventDataLength), + eventData = Buffer.from( + raw.payload.subarray(offset, offset + eventDataLength), ); - switch (this.dataType) { + switch (dataType) { case EntryControlDataTypes.Raw: // RAW 1 to 32 bytes of arbitrary binary data if (!noStrictValidation) { @@ -425,7 +449,6 @@ export class EntryControlCCNotification extends EntryControlCC { eventDataLength >= 1 && eventDataLength <= 32, ); } - this.eventData = eventData; break; case EntryControlDataTypes.ASCII: // ASCII 1 to 32 ASCII encoded characters. ASCII codes MUST be in the value range 0x00-0xF7. @@ -436,26 +459,33 @@ export class EntryControlCCNotification extends EntryControlCC { ); } // Using toString("ascii") converts the padding bytes 0xff to 0x7f - this.eventData = eventData.toString("ascii"); + eventData = eventData.toString("ascii"); if (!noStrictValidation) { validatePayload( - /^[\u0000-\u007f]+[\u007f]*$/.test(this.eventData), + /^[\u0000-\u007f]+[\u007f]*$/.test(eventData), ); } // Trim padding - this.eventData = this.eventData.replace(/[\u007f]*$/, ""); + eventData = eventData.replace(/[\u007f]*$/, ""); break; case EntryControlDataTypes.MD5: // MD5 16 byte binary data encoded as a MD5 hash value. if (!noStrictValidation) { validatePayload(eventDataLength === 16); } - this.eventData = eventData; break; } } else { - this.dataType = EntryControlDataTypes.None; + dataType = EntryControlDataTypes.None; } + + return new EntryControlCCNotification({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + dataType, + eventType, + eventData, + }); } public readonly sequenceNumber: number; @@ -463,7 +493,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,35 +514,52 @@ export class EntryControlCCNotification extends EntryControlCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface EntryControlCCKeySupportedReportOptions { + supportedKeys: number[]; +} + @CCCommand(EntryControlCommand.KeySupportedReport) export class EntryControlCCKeySupportedReport extends EntryControlCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.supportedKeys = options.supportedKeys; + } - validatePayload(this.payload.length >= 1); - const length = this.payload[0]; - validatePayload(this.payload.length >= 1 + length); - this.supportedKeys = parseBitMask( - this.payload.subarray(1, 1 + length), + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EntryControlCCKeySupportedReport { + validatePayload(raw.payload.length >= 1); + const length = raw.payload[0]; + validatePayload(raw.payload.length >= 1 + length); + const supportedKeys = parseBitMask( + raw.payload.subarray(1, 1 + length), 0, ); + + return new EntryControlCCKeySupportedReport({ + nodeId: ctx.sourceNodeId, + supportedKeys, + }); } @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() }, }; } @@ -522,63 +569,91 @@ export class EntryControlCCKeySupportedReport extends EntryControlCC { @expectedCCResponse(EntryControlCCKeySupportedReport) export class EntryControlCCKeySupportedGet extends EntryControlCC {} +// @publicAPI +export interface EntryControlCCEventSupportedReportOptions { + supportedDataTypes: EntryControlDataTypes[]; + supportedEventTypes: EntryControlEventTypes[]; + minKeyCacheSize: number; + maxKeyCacheSize: number; + minKeyCacheTimeout: number; + maxKeyCacheTimeout: number; +} + @CCCommand(EntryControlCommand.EventSupportedReport) export class EntryControlCCEventSupportedReport extends EntryControlCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.supportedDataTypes = options.supportedDataTypes; + this.supportedEventTypes = options.supportedEventTypes; + this.minKeyCacheSize = options.minKeyCacheSize; + this.maxKeyCacheSize = options.maxKeyCacheSize; + this.minKeyCacheTimeout = options.minKeyCacheTimeout; + this.maxKeyCacheTimeout = options.maxKeyCacheTimeout; + } - validatePayload(this.payload.length >= 1); - const dataTypeLength = this.payload[0] & 0b11; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EntryControlCCEventSupportedReport { + validatePayload(raw.payload.length >= 1); + const dataTypeLength = raw.payload[0] & 0b11; let offset = 1; - - validatePayload(this.payload.length >= offset + dataTypeLength); - this.supportedDataTypes = parseBitMask( - this.payload.subarray(offset, offset + dataTypeLength), + validatePayload(raw.payload.length >= offset + dataTypeLength); + const supportedDataTypes: EntryControlDataTypes[] = parseBitMask( + raw.payload.subarray(offset, offset + dataTypeLength), EntryControlDataTypes.None, ); offset += dataTypeLength; - - validatePayload(this.payload.length >= offset + 1); - const eventTypeLength = this.payload[offset] & 0b11111; + validatePayload(raw.payload.length >= offset + 1); + const eventTypeLength = raw.payload[offset] & 0b11111; offset += 1; - - validatePayload(this.payload.length >= offset + eventTypeLength); - this.supportedEventTypes = parseBitMask( - this.payload.subarray(offset, offset + eventTypeLength), + validatePayload(raw.payload.length >= offset + eventTypeLength); + const supportedEventTypes: EntryControlEventTypes[] = parseBitMask( + raw.payload.subarray(offset, offset + eventTypeLength), EntryControlEventTypes.Caching, ); offset += eventTypeLength; - - validatePayload(this.payload.length >= offset + 4); - this.minKeyCacheSize = this.payload[offset]; + validatePayload(raw.payload.length >= offset + 4); + const minKeyCacheSize = raw.payload[offset]; validatePayload( - this.minKeyCacheSize >= 1 && this.minKeyCacheSize <= 32, + minKeyCacheSize >= 1 && minKeyCacheSize <= 32, ); - this.maxKeyCacheSize = this.payload[offset + 1]; + const maxKeyCacheSize = raw.payload[offset + 1]; validatePayload( - this.maxKeyCacheSize >= this.minKeyCacheSize - && this.maxKeyCacheSize <= 32, + maxKeyCacheSize >= minKeyCacheSize + && maxKeyCacheSize <= 32, ); - this.minKeyCacheTimeout = this.payload[offset + 2]; - this.maxKeyCacheTimeout = this.payload[offset + 3]; + const minKeyCacheTimeout = raw.payload[offset + 2]; + const maxKeyCacheTimeout = raw.payload[offset + 3]; + + return new EntryControlCCEventSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedDataTypes, + supportedEventTypes, + minKeyCacheSize, + maxKeyCacheSize, + minKeyCacheTimeout, + maxKeyCacheTimeout, + }); } - 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 +673,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]) @@ -621,19 +696,38 @@ export class EntryControlCCEventSupportedReport extends EntryControlCC { @expectedCCResponse(EntryControlCCEventSupportedReport) export class EntryControlCCEventSupportedGet extends EntryControlCC {} +// @publicAPI +export interface EntryControlCCConfigurationReportOptions { + keyCacheSize: number; + keyCacheTimeout: number; +} + @CCCommand(EntryControlCommand.ConfigurationReport) export class EntryControlCCConfigurationReport extends EntryControlCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); + // TODO: Check implementation: + this.keyCacheSize = options.keyCacheSize; + this.keyCacheTimeout = options.keyCacheTimeout; + } - this.keyCacheSize = this.payload[0]; - validatePayload(this.keyCacheSize >= 1 && this.keyCacheSize <= 32); - this.keyCacheTimeout = this.payload[1]; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): EntryControlCCConfigurationReport { + validatePayload(raw.payload.length >= 2); + const keyCacheSize = raw.payload[0]; + validatePayload(keyCacheSize >= 1 && keyCacheSize <= 32); + const keyCacheTimeout = raw.payload[1]; + + return new EntryControlCCConfigurationReport({ + nodeId: ctx.sourceNodeId, + keyCacheSize, + keyCacheTimeout, + }); } @ccValue(EntryControlCCValues.keyCacheSize) @@ -642,9 +736,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, @@ -658,9 +752,7 @@ export class EntryControlCCConfigurationReport extends EntryControlCC { export class EntryControlCCConfigurationGet extends EntryControlCC {} // @publicAPI -export interface EntryControlCCConfigurationSetOptions - extends CCCommandOptions -{ +export interface EntryControlCCConfigurationSetOptions { keyCacheSize: number; keyCacheTimeout: number; } @@ -669,35 +761,39 @@ export interface EntryControlCCConfigurationSetOptions @useSupervision() export class EntryControlCCConfigurationSet extends EntryControlCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | EntryControlCCConfigurationSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.keyCacheSize = options.keyCacheSize; - this.keyCacheTimeout = options.keyCacheTimeout; - } + super(options); + this.keyCacheSize = options.keyCacheSize; + this.keyCacheTimeout = options.keyCacheTimeout; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): EntryControlCCConfigurationSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new EntryControlCCConfigurationSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..9fb74611ccba 100644 --- a/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts +++ b/packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts @@ -5,14 +5,15 @@ import { type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type WithAddress, ZWaveError, ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { type AllOrNone, @@ -23,10 +24,10 @@ import { import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -41,7 +42,6 @@ import { V } from "../lib/Values"; import { FirmwareDownloadStatus, FirmwareUpdateActivationStatus, - type FirmwareUpdateInitResult, type FirmwareUpdateMetaData, FirmwareUpdateMetaDataCommand, FirmwareUpdateRequestStatus, @@ -113,11 +113,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FirmwareUpdateMetaDataCCMetaDataReport >( cc, @@ -149,55 +149,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, + endpointIndex: 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, + endpointIndex: 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 +197,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, + endpointIndex: 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 +221,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, + endpointIndex: this.endpoint.index, ...options, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FirmwareUpdateMetaDataCCActivationReport >( cc, @@ -263,24 +246,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 +290,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 +304,7 @@ export class FirmwareUpdateMetaDataCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -344,75 +329,93 @@ export class FirmwareUpdateMetaDataCCMetaDataReport implements FirmwareUpdateMetaData { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ( - & FirmwareUpdateMetaDataCCMetaDataReportOptions - & CCCommandOptions - ), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 6); - this.manufacturerId = this.payload.readUInt16BE(0); - this.firmwareId = this.payload.readUInt16BE(2); - this.checksum = this.payload.readUInt16BE(4); - // V1/V2 only have a single firmware which must be upgradable - this.firmwareUpgradable = this.payload[6] === 0xff - || this.payload[6] == undefined; - - if (this.version >= 3 && this.payload.length >= 10) { - this.maxFragmentSize = this.payload.readUInt16BE(8); - // Read variable length list of additional firmwares - const numAdditionalFirmwares = this.payload[7]; - const additionalFirmwareIDs = []; - validatePayload( - this.payload.length >= 10 + 2 * numAdditionalFirmwares, + super(options); + + this.manufacturerId = options.manufacturerId; + this.firmwareId = options.firmwareId ?? 0; + this.checksum = options.checksum ?? 0; + this.firmwareUpgradable = options.firmwareUpgradable; + this.maxFragmentSize = options.maxFragmentSize; + this.additionalFirmwareIDs = options.additionalFirmwareIDs ?? []; + this.hardwareVersion = options.hardwareVersion; + this.continuesToFunction = options.continuesToFunction; + this.supportsActivation = options.supportsActivation; + this.supportsResuming = options.supportsResuming; + this.supportsNonSecureTransfer = options.supportsNonSecureTransfer; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCMetaDataReport { + validatePayload(raw.payload.length >= 6); + const manufacturerId = raw.payload.readUInt16BE(0); + const firmwareId = raw.payload.readUInt16BE(2); + const checksum = raw.payload.readUInt16BE(4); + + // V1/V2 only have a single firmware which must be upgradable + const firmwareUpgradable = raw.payload[6] === 0xff + || raw.payload[6] == undefined; + + let maxFragmentSize: number | undefined; + let additionalFirmwareIDs: number[] | undefined; + let hardwareVersion: number | undefined; + let continuesToFunction: MaybeNotKnown; + let supportsActivation: MaybeNotKnown; + let supportsResuming: MaybeNotKnown; + let supportsNonSecureTransfer: MaybeNotKnown; + + if (raw.payload.length >= 10) { + // V3+ + maxFragmentSize = raw.payload.readUInt16BE(8); + // Read variable length list of additional firmwares + const numAdditionalFirmwares = raw.payload[7]; + additionalFirmwareIDs = []; + validatePayload( + raw.payload.length >= 10 + 2 * numAdditionalFirmwares, + ); + for (let i = 0; i < numAdditionalFirmwares; i++) { + additionalFirmwareIDs.push( + raw.payload.readUInt16BE(10 + 2 * i), ); - for (let i = 0; i < numAdditionalFirmwares; i++) { - additionalFirmwareIDs.push( - this.payload.readUInt16BE(10 + 2 * i), - ); - } - this.additionalFirmwareIDs = additionalFirmwareIDs; - // Read hardware version (if it exists) - let offset = 10 + 2 * numAdditionalFirmwares; - if (this.version >= 5 && this.payload.length >= offset + 1) { - this.hardwareVersion = this.payload[offset]; + } + // Read hardware version (if it exists) + let offset = 10 + 2 * numAdditionalFirmwares; + if (raw.payload.length >= offset + 1) { + // V5+ + hardwareVersion = raw.payload[offset]; + offset++; + if (raw.payload.length >= offset + 1) { + // V6+ + const capabilities = raw.payload[offset]; offset++; - if ( - this.version >= 6 && this.payload.length >= offset + 1 - ) { - 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); - } - } + + continuesToFunction = !!(capabilities & 0b1); + // V7+ + supportsActivation = !!(capabilities & 0b10); + // V8+ + supportsResuming = !!(capabilities & 0b1000); + supportsNonSecureTransfer = !!(capabilities & 0b100); } } - } else { - this.manufacturerId = options.manufacturerId; - this.firmwareId = options.firmwareId ?? 0; - this.checksum = options.checksum ?? 0; - this.firmwareUpgradable = options.firmwareUpgradable; - this.maxFragmentSize = options.maxFragmentSize; - this.additionalFirmwareIDs = options.additionalFirmwareIDs ?? []; - this.hardwareVersion = options.hardwareVersion; - this.continuesToFunction = options.continuesToFunction; - this.supportsActivation = options.supportsActivation; - this.supportsResuming = options.supportsResuming; - this.supportsNonSecureTransfer = options.supportsNonSecureTransfer; } + + return new FirmwareUpdateMetaDataCCMetaDataReport({ + nodeId: ctx.sourceNodeId, + manufacturerId, + firmwareId, + checksum, + firmwareUpgradable, + maxFragmentSize, + additionalFirmwareIDs, + hardwareVersion, + continuesToFunction, + supportsActivation, + supportsResuming, + supportsNonSecureTransfer, + }); } public readonly manufacturerId: number; @@ -433,7 +436,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 +457,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 +493,7 @@ export class FirmwareUpdateMetaDataCCMetaDataReport } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -502,28 +505,54 @@ export class FirmwareUpdateMetaDataCCMetaDataGet extends FirmwareUpdateMetaDataCC {} +// @publicAPI +export interface FirmwareUpdateMetaDataCCRequestReportOptions { + status: FirmwareUpdateRequestStatus; + resume?: boolean; + nonSecureTransfer?: boolean; +} + @CCCommand(FirmwareUpdateMetaDataCommand.RequestReport) export class FirmwareUpdateMetaDataCCRequestReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.status = this.payload[0]; - if (this.payload.length >= 2) { - this.resume = !!(this.payload[1] & 0b100); - this.nonSecureTransfer = !!(this.payload[1] & 0b10); + super(options); + + // TODO: Check implementation: + this.status = options.status; + this.resume = options.resume; + this.nonSecureTransfer = options.nonSecureTransfer; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCRequestReport { + validatePayload(raw.payload.length >= 1); + const status: FirmwareUpdateRequestStatus = raw.payload[0]; + let resume: boolean | undefined; + let nonSecureTransfer: boolean | undefined; + if (raw.payload.length >= 2) { + resume = !!(raw.payload[1] & 0b100); + nonSecureTransfer = !!(raw.payload[1] & 0b10); } + + return new FirmwareUpdateMetaDataCCRequestReport({ + nodeId: ctx.sourceNodeId, + status, + resume, + nonSecureTransfer, + }); } public readonly status: FirmwareUpdateRequestStatus; public resume?: boolean; public nonSecureTransfer?: boolean; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { status: getEnumMemberName( FirmwareUpdateRequestStatus, @@ -537,7 +566,7 @@ export class FirmwareUpdateMetaDataCCRequestReport message["non-secure transfer"] = this.nonSecureTransfer; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -570,33 +599,37 @@ export class FirmwareUpdateMetaDataCCRequestGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (FirmwareUpdateMetaDataCCRequestGetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.manufacturerId = options.manufacturerId; - this.firmwareId = options.firmwareId; - this.checksum = options.checksum; - if ("firmwareTarget" in options) { - this.firmwareTarget = options.firmwareTarget; - this.fragmentSize = options.fragmentSize; - this.activation = options.activation ?? false; - this.hardwareVersion = options.hardwareVersion; - this.resume = options.resume; - this.nonSecureTransfer = options.nonSecureTransfer; - } + super(options); + this.manufacturerId = options.manufacturerId; + this.firmwareId = options.firmwareId; + this.checksum = options.checksum; + if ("firmwareTarget" in options) { + this.firmwareTarget = options.firmwareTarget; + this.fragmentSize = options.fragmentSize; + this.activation = options.activation ?? false; + this.hardwareVersion = options.hardwareVersion; + this.resume = options.resume; + this.nonSecureTransfer = options.nonSecureTransfer; } } + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCRequestGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new FirmwareUpdateMetaDataCCRequestGet({ + // nodeId: ctx.sourceNodeId, + // }); + } + public manufacturerId: number; public firmwareId: number; public checksum: number; @@ -607,36 +640,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,31 +682,52 @@ export class FirmwareUpdateMetaDataCCRequestGet message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface FirmwareUpdateMetaDataCCGetOptions { + numReports: number; + reportNumber: number; +} + @CCCommand(FirmwareUpdateMetaDataCommand.Get) // This is sent to us from the node, so we expect no response export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 3); - this.numReports = this.payload[0]; - this.reportNumber = this.payload.readUInt16BE(1) & 0x7fff; + super(options); + + // TODO: Check implementation: + this.numReports = options.numReports; + this.reportNumber = options.reportNumber; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCGet { + validatePayload(raw.payload.length >= 3); + const numReports = raw.payload[0]; + const reportNumber = raw.payload.readUInt16BE(1) & 0x7fff; + + return new FirmwareUpdateMetaDataCCGet({ + nodeId: ctx.sourceNodeId, + numReports, + reportNumber, + }); } 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, @@ -695,9 +737,7 @@ export class FirmwareUpdateMetaDataCCGet extends FirmwareUpdateMetaDataCC { } // @publicAPI -export interface FirmwareUpdateMetaDataCCReportOptions - extends CCCommandOptions -{ +export interface FirmwareUpdateMetaDataCCReportOptions { isLast: boolean; reportNumber: number; firmwareData: Buffer; @@ -707,30 +747,34 @@ 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, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.reportNumber = options.reportNumber; - this.firmwareData = options.firmwareData; - this.isLast = options.isLast; - } + super(options); + this.reportNumber = options.reportNumber; + this.firmwareData = options.firmwareData; + this.isLast = options.isLast; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCReport { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new FirmwareUpdateMetaDataCCReport({ + // nodeId: ctx.sourceNodeId, + // }); } public isLast: boolean; 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 +784,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 +801,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, @@ -768,27 +815,49 @@ export class FirmwareUpdateMetaDataCCReport extends FirmwareUpdateMetaDataCC { } } +// @publicAPI +export interface FirmwareUpdateMetaDataCCStatusReportOptions { + status: FirmwareUpdateStatus; + waitTime?: number; +} + @CCCommand(FirmwareUpdateMetaDataCommand.StatusReport) export class FirmwareUpdateMetaDataCCStatusReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.status = this.payload[0]; - if (this.payload.length >= 3) { - this.waitTime = this.payload.readUInt16BE(1); + super(options); + + // TODO: Check implementation: + this.status = options.status; + this.waitTime = options.waitTime; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCStatusReport { + validatePayload(raw.payload.length >= 1); + const status: FirmwareUpdateStatus = raw.payload[0]; + let waitTime: number | undefined; + if (raw.payload.length >= 3) { + waitTime = raw.payload.readUInt16BE(1); } + + return new FirmwareUpdateMetaDataCCStatusReport({ + nodeId: ctx.sourceNodeId, + status, + waitTime, + }); } public readonly status: FirmwareUpdateStatus; /** 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,30 +865,65 @@ export class FirmwareUpdateMetaDataCCStatusReport message["wait time"] = `${this.waitTime} seconds`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface FirmwareUpdateMetaDataCCActivationReportOptions { + manufacturerId: number; + firmwareId: number; + checksum: number; + firmwareTarget: number; + activationStatus: FirmwareUpdateActivationStatus; + hardwareVersion?: number; +} + @CCCommand(FirmwareUpdateMetaDataCommand.ActivationReport) export class FirmwareUpdateMetaDataCCActivationReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, 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) { - this.hardwareVersion = this.payload[8]; + super(options); + + // TODO: Check implementation: + this.manufacturerId = options.manufacturerId; + this.firmwareId = options.firmwareId; + this.checksum = options.checksum; + this.firmwareTarget = options.firmwareTarget; + this.activationStatus = options.activationStatus; + this.hardwareVersion = options.hardwareVersion; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCActivationReport { + validatePayload(raw.payload.length >= 8); + const manufacturerId = raw.payload.readUInt16BE(0); + const firmwareId = raw.payload.readUInt16BE(2); + const checksum = raw.payload.readUInt16BE(4); + const firmwareTarget = raw.payload[6]; + const activationStatus: FirmwareUpdateActivationStatus = raw.payload[7]; + let hardwareVersion: number | undefined; + if (raw.payload.length >= 9) { + // V5+ + hardwareVersion = raw.payload[8]; } + + return new FirmwareUpdateMetaDataCCActivationReport({ + nodeId: ctx.sourceNodeId, + manufacturerId, + firmwareId, + checksum, + firmwareTarget, + activationStatus, + hardwareVersion, + }); } public readonly manufacturerId: number; @@ -829,7 +933,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 +948,7 @@ export class FirmwareUpdateMetaDataCCActivationReport message.hardwareVersion = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -866,25 +970,29 @@ export class FirmwareUpdateMetaDataCCActivationSet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (FirmwareUpdateMetaDataCCActivationSetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.manufacturerId = options.manufacturerId; - this.firmwareId = options.firmwareId; - this.checksum = options.checksum; - this.firmwareTarget = options.firmwareTarget; - this.hardwareVersion = options.hardwareVersion; - } + super(options); + this.manufacturerId = options.manufacturerId; + this.firmwareId = options.firmwareId; + this.checksum = options.checksum; + this.firmwareTarget = options.firmwareTarget; + this.hardwareVersion = options.hardwareVersion; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCActivationSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new FirmwareUpdateMetaDataCCActivationSet({ + // nodeId: ctx.sourceNodeId, + // }); } public manufacturerId: number; @@ -893,20 +1001,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,32 +1022,53 @@ export class FirmwareUpdateMetaDataCCActivationSet message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface FirmwareUpdateMetaDataCCPrepareReportOptions { + status: FirmwareDownloadStatus; + checksum: number; +} + @CCCommand(FirmwareUpdateMetaDataCommand.PrepareReport) export class FirmwareUpdateMetaDataCCPrepareReport extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 3); - this.status = this.payload[0]; - this.checksum = this.payload.readUInt16BE(1); + super(options); + + // TODO: Check implementation: + this.status = options.status; + this.checksum = options.checksum; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCPrepareReport { + validatePayload(raw.payload.length >= 3); + const status: FirmwareDownloadStatus = raw.payload[0]; + const checksum = raw.payload.readUInt16BE(1); + + return new FirmwareUpdateMetaDataCCPrepareReport({ + nodeId: ctx.sourceNodeId, + status, + checksum, + }); } 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), @@ -952,9 +1078,7 @@ export class FirmwareUpdateMetaDataCCPrepareReport } // @publicAPI -export interface FirmwareUpdateMetaDataCCPrepareGetOptions - extends CCCommandOptions -{ +export interface FirmwareUpdateMetaDataCCPrepareGetOptions { manufacturerId: number; firmwareId: number; firmwareTarget: number; @@ -968,25 +1092,29 @@ export class FirmwareUpdateMetaDataCCPrepareGet extends FirmwareUpdateMetaDataCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | FirmwareUpdateMetaDataCCPrepareGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.manufacturerId = options.manufacturerId; - this.firmwareId = options.firmwareId; - this.firmwareTarget = options.firmwareTarget; - this.fragmentSize = options.fragmentSize; - this.hardwareVersion = options.hardwareVersion; - } + super(options); + this.manufacturerId = options.manufacturerId; + this.firmwareId = options.firmwareId; + this.firmwareTarget = options.firmwareTarget; + this.fragmentSize = options.fragmentSize; + this.hardwareVersion = options.hardwareVersion; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): FirmwareUpdateMetaDataCCPrepareGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new FirmwareUpdateMetaDataCCPrepareGet({ + // nodeId: ctx.sourceNodeId, + // }); } public manufacturerId: number; @@ -995,19 +1123,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..6b1f5db37314 100644 --- a/packages/cc/src/cc/HumidityControlModeCC.ts +++ b/packages/cc/src/cc/HumidityControlModeCC.ts @@ -5,6 +5,7 @@ import { MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -13,9 +14,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -29,10 +30,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -125,11 +127,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlModeCCReport >( cc, @@ -149,12 +151,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, + endpointIndex: this.endpoint.index, mode, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupportedModes(): Promise< @@ -165,11 +167,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlModeCCSupportedReport >( cc, @@ -185,25 +187,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 +225,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 +240,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), @@ -272,7 +278,7 @@ export class HumidityControlModeCC extends CommandClass { } // @publicAPI -export interface HumidityControlModeCCSetOptions extends CCCommandOptions { +export interface HumidityControlModeCCSetOptions { mode: HumidityControlMode; } @@ -280,33 +286,37 @@ export interface HumidityControlModeCCSetOptions extends CCCommandOptions { @useSupervision() export class HumidityControlModeCCSet extends HumidityControlModeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | HumidityControlModeCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.mode = options.mode; - } + super(options); + this.mode = options.mode; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): HumidityControlModeCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new HumidityControlModeCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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), }, @@ -314,24 +324,41 @@ export class HumidityControlModeCCSet extends HumidityControlModeCC { } } +// @publicAPI +export interface HumidityControlModeCCReportOptions { + mode: HumidityControlMode; +} + @CCCommand(HumidityControlModeCommand.Report) export class HumidityControlModeCCReport extends HumidityControlModeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.mode = options.mode; + } - validatePayload(this.payload.length >= 1); - this.mode = this.payload[0] & 0b1111; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlModeCCReport { + validatePayload(raw.payload.length >= 1); + const mode: HumidityControlMode = raw.payload[0] & 0b1111; + + return new HumidityControlModeCCReport({ + nodeId: ctx.sourceNodeId, + mode, + }); } @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), }, @@ -343,52 +370,65 @@ export class HumidityControlModeCCReport extends HumidityControlModeCC { @expectedCCResponse(HumidityControlModeCCReport) export class HumidityControlModeCCGet extends HumidityControlModeCC {} +// @publicAPI +export interface HumidityControlModeCCSupportedReportOptions { + supportedModes: HumidityControlMode[]; +} + @CCCommand(HumidityControlModeCommand.SupportedReport) export class HumidityControlModeCCSupportedReport extends HumidityControlModeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); - this._supportedModes = parseBitMask( - this.payload, + // TODO: Check implementation: + this.supportedModes = options.supportedModes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlModeCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const supportedModes: HumidityControlMode[] = parseBitMask( + raw.payload, HumidityControlMode.Off, ); - - if (!this._supportedModes.includes(HumidityControlMode.Off)) { - this._supportedModes.unshift(HumidityControlMode.Off); + if (!supportedModes.includes(HumidityControlMode.Off)) { + supportedModes.unshift(HumidityControlMode.Off); } + + return new HumidityControlModeCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedModes, + }); } - 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, - this._supportedModes, + this.supportedModes, ), }); return true; } - private _supportedModes: HumidityControlMode[]; @ccValue(HumidityControlModeCCValues.supportedModes) - public get supportedModes(): readonly HumidityControlMode[] { - return this._supportedModes; - } + public supportedModes: HumidityControlMode[]; - 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..868a14d6ebef 100644 --- a/packages/cc/src/cc/HumidityControlOperatingStateCC.ts +++ b/packages/cc/src/cc/HumidityControlOperatingStateCC.ts @@ -4,14 +4,11 @@ import { type MessageOrCCLogEntry, MessagePriority, ValueMetadata, + type WithAddress, enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -20,8 +17,10 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -89,11 +88,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlOperatingStateCCReport >( cc, @@ -111,41 +110,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( @@ -158,26 +161,43 @@ export class HumidityControlOperatingStateCC extends CommandClass { } } +// @publicAPI +export interface HumidityControlOperatingStateCCReportOptions { + state: HumidityControlOperatingState; +} + @CCCommand(HumidityControlOperatingStateCommand.Report) export class HumidityControlOperatingStateCCReport extends HumidityControlOperatingStateCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.state = options.state; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlOperatingStateCCReport { + validatePayload(raw.payload.length >= 1); + const state: HumidityControlOperatingState = raw.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1); - this.state = this.payload[0] & 0b1111; + return new HumidityControlOperatingStateCCReport({ + nodeId: ctx.sourceNodeId, + state, + }); } @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..0e1aaf1fdde3 100644 --- a/packages/cc/src/cc/HumidityControlSetpointCC.ts +++ b/packages/cc/src/cc/HumidityControlSetpointCC.ts @@ -7,6 +7,7 @@ import { type SupervisionResult, ValueMetadata, type ValueMetadataNumeric, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeFloatWithScale, @@ -18,9 +19,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -34,10 +35,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -206,12 +208,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, + endpointIndex: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCReport >( cc, @@ -239,14 +241,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, + endpointIndex: this.endpoint.index, setpointType, value, scale, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -258,12 +260,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, + endpointIndex: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCCapabilitiesReport >( cc, @@ -287,11 +289,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< HumidityControlSetpointCCSupportedReport >( cc, @@ -309,15 +311,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, + endpointIndex: this.endpoint.index, + setpointType, + }); + const response = await this.host.sendCommand< HumidityControlSetpointCCScaleSupportedReport >( cc, @@ -336,7 +335,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 +345,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 +370,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 +386,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 +407,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 +422,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 +434,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 +451,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 +460,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 +491,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 +503,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", @@ -511,7 +514,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; } // @publicAPI -export interface HumidityControlSetpointCCSetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCSetOptions { setpointType: HumidityControlSetpointType; value: number; scale: number; @@ -521,41 +524,45 @@ export interface HumidityControlSetpointCCSetOptions extends CCCommandOptions { @useSupervision() export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | HumidityControlSetpointCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.setpointType = options.setpointType; - this.value = options.value; - this.scale = options.scale; - } + super(options); + this.setpointType = options.setpointType; + this.value = options.value; + this.scale = options.scale; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): HumidityControlSetpointCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new HumidityControlSetpointCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } public setpointType: HumidityControlSetpointType; 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, @@ -567,32 +574,57 @@ export class HumidityControlSetpointCCSet extends HumidityControlSetpointCC { } } +// @publicAPI +export interface HumidityControlSetpointCCReportOptions { + type: HumidityControlSetpointType; + scale: number; + value: number; +} + @CCCommand(HumidityControlSetpointCommand.Report) export class HumidityControlSetpointCCReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.type = options.type; + this.value = options.value; + this.scale = options.scale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlSetpointCCReport { + validatePayload(raw.payload.length >= 1); + const type: HumidityControlSetpointType = raw.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1); - this._type = this.payload[0] & 0b1111; // Setpoint type 0 is not defined in the spec, prevent devices from using it. - if (this._type === 0) { + if (type === 0) { // Not supported - this._value = 0; - this.scale = 0; - return; + return new HumidityControlSetpointCCReport({ + nodeId: ctx.sourceNodeId, + type, + value: 0, + scale: 0, + }); } // parseFloatWithScale does its own validation - const { value, scale } = parseFloatWithScale(this.payload.subarray(1)); - this._value = value; - this.scale = scale; + const { value, scale } = parseFloatWithScale(raw.payload.subarray(1)); + + return new HumidityControlSetpointCCReport({ + nodeId: ctx.sourceNodeId, + type, + value, + 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,44 +632,36 @@ 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, ); return true; } - private _type: HumidityControlSetpointType; - public get type(): HumidityControlSetpointType { - return this._type; - } - - public readonly scale: number; - - private _value: number; - public get value(): number { - return this._value; - } + public type: HumidityControlSetpointType; + public scale: number; + public value: number; - 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, @@ -658,7 +682,7 @@ function testResponseForHumidityControlSetpointGet( } // @publicAPI -export interface HumidityControlSetpointCCGetOptions extends CCCommandOptions { +export interface HumidityControlSetpointCCGetOptions { setpointType: HumidityControlSetpointType; } @@ -669,33 +693,37 @@ export interface HumidityControlSetpointCCGetOptions extends CCCommandOptions { ) export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | HumidityControlSetpointCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.setpointType = options.setpointType; - } + super(options); + this.setpointType = options.setpointType; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): HumidityControlSetpointCCGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new HumidityControlSetpointCCGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, @@ -706,30 +734,48 @@ export class HumidityControlSetpointCCGet extends HumidityControlSetpointCC { } } +// @publicAPI +export interface HumidityControlSetpointCCSupportedReportOptions { + supportedSetpointTypes: HumidityControlSetpointType[]; +} + @CCCommand(HumidityControlSetpointCommand.SupportedReport) export class HumidityControlSetpointCCSupportedReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); - this.supportedSetpointTypes = parseBitMask( - this.payload, - HumidityControlSetpointType["N/A"], - ); + // TODO: Check implementation: + this.supportedSetpointTypes = options.supportedSetpointTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlSetpointCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const supportedSetpointTypes: HumidityControlSetpointType[] = + parseBitMask( + raw.payload, + HumidityControlSetpointType["N/A"], + ); + + return new HumidityControlSetpointCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedSetpointTypes, + }); } @ccValue(HumidityControlSetpointCCValues.supportedSetpointTypes) 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( @@ -753,32 +799,50 @@ export class HumidityControlSetpointCCSupportedGet extends HumidityControlSetpointCC {} +// @publicAPI +export interface HumidityControlSetpointCCScaleSupportedReportOptions { + supportedScales: number[]; +} + @CCCommand(HumidityControlSetpointCommand.ScaleSupportedReport) export class HumidityControlSetpointCCScaleSupportedReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress< + HumidityControlSetpointCCScaleSupportedReportOptions + >, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); + // TODO: Check implementation: + this.supportedScales = options.supportedScales; + } - this.supportedScales = parseBitMask( - Buffer.from([this.payload[0] & 0b1111]), + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlSetpointCCScaleSupportedReport { + validatePayload(raw.payload.length >= 1); + const supportedScales = parseBitMask( + Buffer.from([raw.payload[0] & 0b1111]), 0, ); + + return new HumidityControlSetpointCCScaleSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedScales, + }); } 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}`) @@ -789,9 +853,7 @@ export class HumidityControlSetpointCCScaleSupportedReport } // @publicAPI -export interface HumidityControlSetpointCCScaleSupportedGetOptions - extends CCCommandOptions -{ +export interface HumidityControlSetpointCCScaleSupportedGetOptions { setpointType: HumidityControlSetpointType; } @@ -801,33 +863,37 @@ export class HumidityControlSetpointCCScaleSupportedGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | HumidityControlSetpointCCScaleSupportedGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.setpointType = options.setpointType; - } + super(options); + this.setpointType = options.setpointType; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): HumidityControlSetpointCCScaleSupportedGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new HumidityControlSetpointCCScaleSupportedGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, @@ -838,77 +904,89 @@ export class HumidityControlSetpointCCScaleSupportedGet } } +// @publicAPI +export interface HumidityControlSetpointCCCapabilitiesReportOptions { + type: HumidityControlSetpointType; + minValue: number; + maxValue: number; + minValueScale: number; + maxValueScale: number; +} + @CCCommand(HumidityControlSetpointCommand.CapabilitiesReport) export class HumidityControlSetpointCCCapabilitiesReport extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress< + HumidityControlSetpointCCCapabilitiesReportOptions + >, ) { - super(host, options); + super(options); + + this.type = options.type; + this.minValue = options.minValue; + this.maxValue = options.maxValue; + this.minValueScale = options.minValueScale; + this.maxValueScale = options.maxValueScale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): HumidityControlSetpointCCCapabilitiesReport { + validatePayload(raw.payload.length >= 1); + const type: HumidityControlSetpointType = raw.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1); - this._type = this.payload[0] & 0b1111; - let bytesRead: number; // parseFloatWithScale does its own validation - ({ - value: this._minValue, - scale: this._minValueScale, + const { + value: minValue, + scale: minValueScale, bytesRead, - } = parseFloatWithScale(this.payload.subarray(1))); - ({ value: this._maxValue, scale: this._maxValueScale } = - parseFloatWithScale(this.payload.subarray(1 + bytesRead))); + } = parseFloatWithScale(raw.payload.subarray(1)); + const { value: maxValue, scale: maxValueScale } = parseFloatWithScale( + raw.payload.subarray(1 + bytesRead), + ); + + return new HumidityControlSetpointCCCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + type, + minValue, + minValueScale, + maxValue, + maxValueScale, + }); } - 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, - unit: getSetpointUnit(this._minValueScale) - || getSetpointUnit(this._maxValueScale), + min: this.minValue, + max: this.maxValue, + unit: getSetpointUnit(this.minValueScale) + || getSetpointUnit(this.maxValueScale), }); return true; } - private _type: HumidityControlSetpointType; - public get type(): HumidityControlSetpointType { - return this._type; - } - - private _minValue: number; - public get minValue(): number { - return this._minValue; - } - - private _maxValue: number; - public get maxValue(): number { - return this._maxValue; - } - - private _minValueScale: number; - public get minValueScale(): number { - return this._minValueScale; - } - - private _maxValueScale: number; - public get maxValueScale(): number { - return this._maxValueScale; - } + public type: HumidityControlSetpointType; + public minValue: number; + public maxValue: number; + public minValueScale: number; + public maxValueScale: number; - 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, @@ -922,9 +1000,7 @@ export class HumidityControlSetpointCCCapabilitiesReport } // @publicAPI -export interface HumidityControlSetpointCCCapabilitiesGetOptions - extends CCCommandOptions -{ +export interface HumidityControlSetpointCCCapabilitiesGetOptions { setpointType: HumidityControlSetpointType; } @@ -934,33 +1010,37 @@ export class HumidityControlSetpointCCCapabilitiesGet extends HumidityControlSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | HumidityControlSetpointCCCapabilitiesGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.setpointType = options.setpointType; - } + super(options); + this.setpointType = options.setpointType; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): HumidityControlSetpointCCCapabilitiesGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new HumidityControlSetpointCCCapabilitiesGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..e295314cb6ac 100644 --- a/packages/cc/src/cc/InclusionControllerCC.ts +++ b/packages/cc/src/cc/InclusionControllerCC.ts @@ -1,18 +1,18 @@ import { CommandClasses, type MessageOrCCLogEntry, + type WithAddress, validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host"; +import type { + CCEncodingContext, + CCParsingContext, + GetValueDB, +} from "@zwave-js/host"; import { getEnumMemberName } from "@zwave-js/shared"; import { CCAPI } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -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, + endpointIndex: 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,18 +76,18 @@ 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, + endpointIndex: this.endpoint.index, step, status, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } // @publicAPI -export interface InclusionControllerCCCompleteOptions extends CCCommandOptions { +export interface InclusionControllerCCCompleteOptions { step: InclusionControllerStep; status: InclusionControllerStatus; } @@ -95,36 +95,43 @@ export interface InclusionControllerCCCompleteOptions extends CCCommandOptions { @CCCommand(InclusionControllerCommand.Complete) export class InclusionControllerCCComplete extends InclusionControllerCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | InclusionControllerCCCompleteOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.step = this.payload[0]; - validatePayload.withReason("Invalid inclusion controller step")( - this.step in InclusionControllerStep, - ); - this.status = this.payload[1]; - } else { - this.step = options.step; - this.status = options.status; - } + super(options); + this.step = options.step; + this.status = options.status; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): InclusionControllerCCComplete { + validatePayload(raw.payload.length >= 2); + const step: InclusionControllerStep = raw.payload[0]; + + validatePayload.withReason("Invalid inclusion controller step")( + step in InclusionControllerStep, + ); + const status: InclusionControllerStatus = raw.payload[1]; + + return new InclusionControllerCCComplete({ + nodeId: ctx.sourceNodeId, + step, + status, + }); } 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( @@ -137,7 +144,7 @@ export class InclusionControllerCCComplete extends InclusionControllerCC { } // @publicAPI -export interface InclusionControllerCCInitiateOptions extends CCCommandOptions { +export interface InclusionControllerCCInitiateOptions { includedNodeId: number; step: InclusionControllerStep; } @@ -145,36 +152,43 @@ export interface InclusionControllerCCInitiateOptions extends CCCommandOptions { @CCCommand(InclusionControllerCommand.Initiate) export class InclusionControllerCCInitiate extends InclusionControllerCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | InclusionControllerCCInitiateOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.includedNodeId = this.payload[0]; - this.step = this.payload[1]; - validatePayload.withReason("Invalid inclusion controller step")( - this.step in InclusionControllerStep, - ); - } else { - this.includedNodeId = options.includedNodeId; - this.step = options.step; - } + super(options); + this.includedNodeId = options.includedNodeId; + this.step = options.step; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): InclusionControllerCCInitiate { + validatePayload(raw.payload.length >= 2); + const includedNodeId = raw.payload[0]; + const step: InclusionControllerStep = raw.payload[1]; + + validatePayload.withReason("Invalid inclusion controller step")( + step in InclusionControllerStep, + ); + + return new InclusionControllerCCInitiate({ + nodeId: ctx.sourceNodeId, + includedNodeId, + step, + }); } 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..e54b09efa770 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, @@ -9,6 +8,7 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeBitMask, @@ -17,9 +17,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -35,10 +35,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -202,7 +203,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 +306,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 +359,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, + endpointIndex: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -380,30 +379,47 @@ 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, + endpointIndex: this.endpoint.index, ...(typeof value === "number" ? { value } : { values: value }), }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() public async sendReport( - options: IndicatorCCReportSpecificOptions, + options: IndicatorCCReportOptions, ): Promise { this.assertSupportsCommand( IndicatorCommand, IndicatorCommand.Report, ); - const cc = new IndicatorCCReport(this.applHost, { + const cc = new IndicatorCCReport({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -420,12 +436,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, + endpointIndex: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IndicatorCCSupportedReport >( cc, @@ -454,15 +470,15 @@ 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, + endpointIndex: this.endpoint.index, indicatorId, supportedProperties, nextIndicatorId, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -475,14 +491,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, + endpointIndex: this.endpoint.index, indicatorId, description, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -551,7 +567,7 @@ export class IndicatorCCAPI extends CCAPI { } const supportedPropertyIDs = IndicatorCC.getSupportedPropertyIDsCached( - this.applHost, + this.host, this.endpoint, indicatorId, ); @@ -655,12 +671,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, + endpointIndex: this.endpoint.index, indicatorId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IndicatorCCDescriptionReport >( cc, @@ -676,25 +692,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 +723,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 +740,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 +749,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 +774,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 +802,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 +821,7 @@ export class IndicatorCC extends CommandClass { } public translatePropertyKey( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey: string | number, ): string | undefined { @@ -816,11 +836,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 +848,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 +862,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( @@ -863,6 +883,7 @@ export class IndicatorCC extends CommandClass { } } +// @publicAPI export interface IndicatorObject { indicatorId: number; propertyId: number; @@ -882,63 +903,55 @@ export type IndicatorCCSetOptions = @useSupervision() export class IndicatorCCSet extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (IndicatorCCSetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - - const objCount = this.payload.length >= 2 - ? this.payload[1] & 0b11111 - : 0; - if (objCount === 0) { - this.indicator0Value = this.payload[0]; - } else { - validatePayload(this.payload.length >= 2 + 3 * objCount); - this.values = []; - for (let i = 0; i < objCount; i++) { - const offset = 2 + 3 * i; - const value: IndicatorObject = { - indicatorId: this.payload[offset], - propertyId: this.payload[offset + 1], - value: this.payload[offset + 2], - }; - this.values.push(value); - } - } + super(options); + if ("value" in options) { + this.indicator0Value = options.value; } 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, - ); - } - 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; } } + public static from(raw: CCRaw, ctx: CCParsingContext): IndicatorCCSet { + validatePayload(raw.payload.length >= 1); + + const objCount = raw.payload.length >= 2 + ? raw.payload[1] & 0b11111 + : 0; + + if (objCount === 0) { + const indicator0Value = raw.payload[0]; + + return new IndicatorCCSet({ + nodeId: ctx.sourceNodeId, + value: indicator0Value, + }); + } + + validatePayload(raw.payload.length >= 2 + 3 * objCount); + + const values: IndicatorObject[] = []; + for (let i = 0; i < objCount; i++) { + const offset = 2 + 3 * i; + const value: IndicatorObject = { + indicatorId: raw.payload[offset], + propertyId: raw.payload[offset + 1], + value: raw.payload[offset + 2], + }; + values.push(value); + } + + return new IndicatorCCSet({ + nodeId: ctx.sourceNodeId, + values, + }); + } + 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 +973,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,14 +994,14 @@ export class IndicatorCCSet extends IndicatorCC { }`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export type IndicatorCCReportSpecificOptions = +export type IndicatorCCReportOptions = | { value: number; } @@ -999,88 +1012,96 @@ export type IndicatorCCReportSpecificOptions = @CCCommand(IndicatorCommand.Report) export class IndicatorCCReport extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (IndicatorCCReportSpecificOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); + + if ("value" in options) { + this.indicator0Value = options.value; + } else if ("values" in options) { + 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; + } + } - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); + public static from(raw: CCRaw, ctx: CCParsingContext): IndicatorCCReport { + validatePayload(raw.payload.length >= 1); - const objCount = this.payload.length >= 2 - ? this.payload[1] & 0b11111 - : 0; - if (objCount === 0) { - this.indicator0Value = this.payload[0]; - } else { - validatePayload(this.payload.length >= 2 + 3 * objCount); - this.values = []; - for (let i = 0; i < objCount; i++) { - const offset = 2 + 3 * i; - const value: IndicatorObject = { - indicatorId: this.payload[offset], - propertyId: this.payload[offset + 1], - value: this.payload[offset + 2], - }; - this.values.push(value); - } + const objCount = raw.payload.length >= 2 + ? raw.payload[1] & 0b11111 + : 0; - // TODO: Think if we want this: - - // // If not all Property IDs are included in the command for the actual Indicator ID, - // // a controlling node MUST assume non-specified Property IDs values to be 0x00. - // const indicatorId = this.values[0].indicatorId; - // const supportedIndicatorProperties = - // valueDB.getValue( - // getSupportedPropertyIDsValueID( - // this.endpointIndex, - // indicatorId, - // ), - // ) ?? []; - // // Find out which ones are missing - // const missingIndicatorProperties = supportedIndicatorProperties.filter( - // prop => - // !this.values!.find(({ propertyId }) => prop === propertyId), - // ); - // // And assume they are 0 (false) - // for (const missing of missingIndicatorProperties) { - // this.setIndicatorValue({ - // indicatorId, - // propertyId: missing, - // value: 0, - // }); - // } - } - } else { - if ("value" in options) { - this.indicator0Value = options.value; - } else if ("values" in options) { - 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; - } + if (objCount === 0) { + const indicator0Value = raw.payload[0]; + return new IndicatorCCReport({ + nodeId: ctx.sourceNodeId, + value: indicator0Value, + }); } + + validatePayload(raw.payload.length >= 2 + 3 * objCount); + + const values: IndicatorObject[] = []; + for (let i = 0; i < objCount; i++) { + const offset = 2 + 3 * i; + const value: IndicatorObject = { + indicatorId: raw.payload[offset], + propertyId: raw.payload[offset + 1], + value: raw.payload[offset + 2], + }; + values.push(value); + } + + return new IndicatorCCReport({ + nodeId: ctx.sourceNodeId, + values, + }); + + // TODO: Think if we want this: + + // // If not all Property IDs are included in the command for the actual Indicator ID, + // // a controlling node MUST assume non-specified Property IDs values to be 0x00. + // const indicatorId = this.values[0].indicatorId; + // const supportedIndicatorProperties = + // valueDB.getValue( + // getSupportedPropertyIDsValueID( + // this.endpointIndex, + // indicatorId, + // ), + // ) ?? []; + // // Find out which ones are missing + // const missingIndicatorProperties = supportedIndicatorProperties.filter( + // prop => + // !this.values!.find(({ propertyId }) => prop === propertyId), + // ); + // // And assume they are 0 (false) + // for (const missing of missingIndicatorProperties) { + // this.setIndicatorValue({ + // indicatorId, + // propertyId: missing, + // value: 0, + // }); + // } } - 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 +1112,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 +1126,7 @@ export class IndicatorCCReport extends IndicatorCC { if (timeout?.seconds) timeoutString += `${timeout.seconds}s`; this.setValue( - applHost, + ctx, IndicatorCCValues.timeout, timeoutString, ); @@ -1119,7 +1140,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 +1148,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 +1168,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 +1194,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,14 +1215,14 @@ export class IndicatorCCReport extends IndicatorCC { }`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface IndicatorCCGetOptions extends CCCommandOptions { +export interface IndicatorCCGetOptions { indicatorId?: number; } @@ -1210,31 +1230,37 @@ export interface IndicatorCCGetOptions extends CCCommandOptions { @expectedCCResponse(IndicatorCCReport) export class IndicatorCCGet extends IndicatorCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | IndicatorCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length > 0) { - this.indicatorId = this.payload[0]; - } - } else { - this.indicatorId = options.indicatorId; + super(options); + this.indicatorId = options.indicatorId; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): IndicatorCCGet { + let indicatorId: number | undefined; + + if (raw.payload.length > 0) { + indicatorId = raw.payload[0]; } + + return new IndicatorCCGet({ + nodeId: ctx.sourceNodeId, + indicatorId, + }); } 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), }, @@ -1243,7 +1269,7 @@ export class IndicatorCCGet extends IndicatorCC { } // @publicAPI -export interface IndicatorCCSupportedReportOptions extends CCCommandOptions { +export interface IndicatorCCSupportedReportOptions { indicatorId: number; nextIndicatorId: number; supportedProperties: readonly number[]; @@ -1252,42 +1278,51 @@ export interface IndicatorCCSupportedReportOptions extends CCCommandOptions { @CCCommand(IndicatorCommand.SupportedReport) export class IndicatorCCSupportedReport extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IndicatorCCSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.indicatorId = this.payload[0]; - this.nextIndicatorId = this.payload[1]; - const bitMaskLength = this.payload[2] & 0b11111; - if (bitMaskLength === 0) { - this.supportedProperties = []; - } else { - validatePayload(this.payload.length >= 3 + bitMaskLength); - // The bit mask starts at 0, but bit 0 is not used - this.supportedProperties = parseBitMask( - this.payload.subarray(3, 3 + bitMaskLength), - 0, - ).filter((v) => v !== 0); - } + super(options); + + this.indicatorId = options.indicatorId; + this.nextIndicatorId = options.nextIndicatorId; + this.supportedProperties = options.supportedProperties; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IndicatorCCSupportedReport { + validatePayload(raw.payload.length >= 3); + const indicatorId = raw.payload[0]; + const nextIndicatorId = raw.payload[1]; + const bitMaskLength = raw.payload[2] & 0b11111; + let supportedProperties: readonly number[]; + + if (bitMaskLength === 0) { + supportedProperties = []; } else { - this.indicatorId = options.indicatorId; - this.nextIndicatorId = options.nextIndicatorId; - this.supportedProperties = options.supportedProperties; + validatePayload(raw.payload.length >= 3 + bitMaskLength); + // The bit mask starts at 0, but bit 0 is not used + supportedProperties = parseBitMask( + raw.payload.subarray(3, 3 + bitMaskLength), + 0, + ).filter((v) => v !== 0); } + + return new IndicatorCCSupportedReport({ + nodeId: ctx.sourceNodeId, + indicatorId, + nextIndicatorId, + supportedProperties, + }); } - 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 +1334,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 +1347,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": `${ @@ -1336,7 +1371,7 @@ export class IndicatorCCSupportedReport extends IndicatorCC { } // @publicAPI -export interface IndicatorCCSupportedGetOptions extends CCCommandOptions { +export interface IndicatorCCSupportedGetOptions { indicatorId: number; } @@ -1354,30 +1389,35 @@ function testResponseForIndicatorSupportedGet( ) export class IndicatorCCSupportedGet extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IndicatorCCSupportedGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.indicatorId = this.payload[0]; - } else { - this.indicatorId = options.indicatorId; - } + super(options); + this.indicatorId = options.indicatorId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IndicatorCCSupportedGet { + validatePayload(raw.payload.length >= 1); + const indicatorId = raw.payload[0]; + + return new IndicatorCCSupportedGet({ + nodeId: ctx.sourceNodeId, + indicatorId, + }); } 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,36 +1434,42 @@ export interface IndicatorCCDescriptionReportOptions { @CCCommand(IndicatorCommand.DescriptionReport) export class IndicatorCCDescriptionReport extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (IndicatorCCDescriptionReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.indicatorId = this.payload[0]; - const descrptionLength = this.payload[1]; - validatePayload(this.payload.length >= 2 + descrptionLength); - this.description = this.payload - .subarray(2, 2 + descrptionLength) - .toString("utf8"); - } else { - this.indicatorId = options.indicatorId; - this.description = options.description; - } + super(options); + + this.indicatorId = options.indicatorId; + this.description = options.description; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IndicatorCCDescriptionReport { + validatePayload(raw.payload.length >= 2); + const indicatorId = raw.payload[0]; + const descrptionLength = raw.payload[1]; + validatePayload(raw.payload.length >= 2 + descrptionLength); + const description: string = raw.payload + .subarray(2, 2 + descrptionLength) + .toString("utf8"); + + return new IndicatorCCDescriptionReport({ + nodeId: ctx.sourceNodeId, + indicatorId, + description, + }); } 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 +1478,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)", @@ -1453,7 +1499,7 @@ export class IndicatorCCDescriptionReport extends IndicatorCC { } // @publicAPI -export interface IndicatorCCDescriptionGetOptions extends CCCommandOptions { +export interface IndicatorCCDescriptionGetOptions { indicatorId: number; } @@ -1471,36 +1517,41 @@ function testResponseForIndicatorDescriptionGet( ) export class IndicatorCCDescriptionGet extends IndicatorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IndicatorCCDescriptionGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.indicatorId = this.payload[0]; - } else { - this.indicatorId = options.indicatorId; - if (!isManufacturerDefinedIndicator(this.indicatorId)) { - throw new ZWaveError( - "The indicator ID must be between 0x80 and 0x9f", - ZWaveErrorCodes.Argument_Invalid, - ); - } + super(options); + this.indicatorId = options.indicatorId; + if (!isManufacturerDefinedIndicator(this.indicatorId)) { + throw new ZWaveError( + "The indicator ID must be between 0x80 and 0x9f", + ZWaveErrorCodes.Argument_Invalid, + ); } } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IndicatorCCDescriptionGet { + validatePayload(raw.payload.length >= 1); + const indicatorId = raw.payload[0]; + + return new IndicatorCCDescriptionGet({ + nodeId: ctx.sourceNodeId, + indicatorId, + }); + } + 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..27d060b5d16d 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, @@ -8,6 +8,7 @@ import { type SupervisionResult, type ValueID, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeFloatWithScale, @@ -16,9 +17,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -35,10 +36,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -602,11 +604,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemInfoReport >( cc, @@ -629,11 +631,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemStatusReport >( cc, @@ -667,11 +669,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCSystemConfigReport >( cc, @@ -697,13 +699,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, + endpointIndex: this.endpoint.index, ...config, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -714,12 +716,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, + endpointIndex: this.endpoint.index, valveId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveInfoReport >( cc, @@ -748,13 +750,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -765,12 +767,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, + endpointIndex: this.endpoint.index, valveId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveConfigReport >( cc, @@ -799,14 +801,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, + endpointIndex: this.endpoint.index, valveId, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -828,7 +830,7 @@ export class IrrigationCCAPI extends CCAPI { if (!this.endpoint.virtual) { const maxValveTableSize = IrrigationCC.getMaxValveTableSizeCached( - this.applHost, + this.host, this.endpoint, ); if ( @@ -842,14 +844,14 @@ export class IrrigationCCAPI extends CCAPI { } } - const cc = new IrrigationCCValveTableSet(this.applHost, { + const cc = new IrrigationCCValveTableSet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, tableId, entries, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -861,12 +863,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, + endpointIndex: this.endpoint.index, tableId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< IrrigationCCValveTableReport >( cc, @@ -886,13 +888,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, + endpointIndex: this.endpoint.index, tableIDs, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -908,13 +910,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, + endpointIndex: 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 +1120,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 +1135,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 +1148,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 +1197,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 +1206,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 +1267,7 @@ moisture sensor polarity: ${ ) }`; } - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -1270,7 +1276,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 +1284,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 +1302,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 +1318,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 +1334,7 @@ moisture sensor polarity: ${ } public translateProperty( - applHost: ZWaveApplicationHost, + ctx: GetValueDB, property: string | number, propertyKey?: string | number, ): string { @@ -1337,22 +1343,49 @@ 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); } } +// @publicAPI +export interface IrrigationCCSystemInfoReportOptions { + supportsMasterValve: boolean; + numValves: number; + numValveTables: number; + maxValveTableSize: number; +} + @CCCommand(IrrigationCommand.SystemInfoReport) export class IrrigationCCSystemInfoReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 4); - this.supportsMasterValve = !!(this.payload[0] & 0x01); - this.numValves = this.payload[1]; - this.numValveTables = this.payload[2]; - this.maxValveTableSize = this.payload[3] & 0b1111; + super(options); + + // TODO: Check implementation: + this.supportsMasterValve = options.supportsMasterValve; + this.numValves = options.numValves; + this.numValveTables = options.numValveTables; + this.maxValveTableSize = options.maxValveTableSize; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCSystemInfoReport { + validatePayload(raw.payload.length >= 4); + const supportsMasterValve = !!(raw.payload[0] & 0x01); + const numValves = raw.payload[1]; + const numValveTables = raw.payload[2]; + const maxValveTableSize = raw.payload[3] & 0b1111; + + return new IrrigationCCSystemInfoReport({ + nodeId: ctx.sourceNodeId, + supportsMasterValve, + numValves, + numValveTables, + maxValveTableSize, + }); } @ccValue(IrrigationCCValues.numValves) @@ -1367,9 +1400,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, @@ -1384,49 +1417,112 @@ export class IrrigationCCSystemInfoReport extends IrrigationCC { @expectedCCResponse(IrrigationCCSystemInfoReport) export class IrrigationCCSystemInfoGet extends IrrigationCC {} +// @publicAPI +export interface IrrigationCCSystemStatusReportOptions { + systemVoltage: number; + flowSensorActive: boolean; + pressureSensorActive: boolean; + rainSensorActive: boolean; + moistureSensorActive: boolean; + flow?: number; + pressure?: number; + shutoffDuration: number; + errorNotProgrammed: boolean; + errorEmergencyShutdown: boolean; + errorHighPressure: boolean; + errorLowPressure: boolean; + errorValve: boolean; + masterValveOpen: boolean; + firstOpenZoneId?: number; +} + @CCCommand(IrrigationCommand.SystemStatusReport) export class IrrigationCCSystemStatusReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 2); - this.systemVoltage = this.payload[0]; - this.flowSensorActive = !!(this.payload[1] & 0x01); - this.pressureSensorActive = !!(this.payload[1] & 0x02); - this.rainSensorActive = !!(this.payload[1] & 0x04); - this.moistureSensorActive = !!(this.payload[1] & 0x08); - + super(options); + + // TODO: Check implementation: + this.systemVoltage = options.systemVoltage; + this.flowSensorActive = options.flowSensorActive; + this.pressureSensorActive = options.pressureSensorActive; + this.rainSensorActive = options.rainSensorActive; + this.moistureSensorActive = options.moistureSensorActive; + this.flow = options.flow; + this.pressure = options.pressure; + this.shutoffDuration = options.shutoffDuration; + this.errorNotProgrammed = options.errorNotProgrammed; + this.errorEmergencyShutdown = options.errorEmergencyShutdown; + this.errorHighPressure = options.errorHighPressure; + this.errorLowPressure = options.errorLowPressure; + this.errorValve = options.errorValve; + this.masterValveOpen = options.masterValveOpen; + this.firstOpenZoneId = options.firstOpenZoneId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCSystemStatusReport { + validatePayload(raw.payload.length >= 2); + const systemVoltage = raw.payload[0]; + const flowSensorActive = !!(raw.payload[1] & 0x01); + const pressureSensorActive = !!(raw.payload[1] & 0x02); + const rainSensorActive = !!(raw.payload[1] & 0x04); + const moistureSensorActive = !!(raw.payload[1] & 0x08); let offset = 2; + let flow: number | undefined; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0); - if (this.flowSensorActive) this.flow = value; + if (flowSensorActive) flow = value; offset += bytesRead; } + + let pressure: number | undefined; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0); - if (this.pressureSensorActive) this.pressure = value; + if (pressureSensorActive) pressure = value; offset += bytesRead; } - validatePayload(this.payload.length >= offset + 4); - this.shutoffDuration = this.payload[offset]; - this.errorNotProgrammed = !!(this.payload[offset + 1] & 0x01); - this.errorEmergencyShutdown = !!(this.payload[offset + 1] & 0x02); - this.errorHighPressure = !!(this.payload[offset + 1] & 0x04); - this.errorLowPressure = !!(this.payload[offset + 1] & 0x08); - this.errorValve = !!(this.payload[offset + 1] & 0x10); - this.masterValveOpen = !!(this.payload[offset + 2] & 0x01); - if (this.payload[offset + 3]) { - this.firstOpenZoneId = this.payload[offset + 3]; + validatePayload(raw.payload.length >= offset + 4); + const shutoffDuration = raw.payload[offset]; + const errorNotProgrammed = !!(raw.payload[offset + 1] & 0x01); + const errorEmergencyShutdown = !!(raw.payload[offset + 1] & 0x02); + const errorHighPressure = !!(raw.payload[offset + 1] & 0x04); + const errorLowPressure = !!(raw.payload[offset + 1] & 0x08); + const errorValve = !!(raw.payload[offset + 1] & 0x10); + const masterValveOpen = !!(raw.payload[offset + 2] & 0x01); + let firstOpenZoneId: number | undefined; + if (raw.payload[offset + 3]) { + firstOpenZoneId = raw.payload[offset + 3]; } + + return new IrrigationCCSystemStatusReport({ + nodeId: ctx.sourceNodeId, + systemVoltage, + flowSensorActive, + pressureSensorActive, + rainSensorActive, + moistureSensorActive, + flow, + pressure, + shutoffDuration, + errorNotProgrammed, + errorEmergencyShutdown, + errorHighPressure, + errorLowPressure, + errorValve, + masterValveOpen, + firstOpenZoneId, + }); } @ccValue(IrrigationCCValues.systemVoltage) @@ -1474,7 +1570,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 +1611,7 @@ export class IrrigationCCSystemStatusReport extends IrrigationCC { } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1538,25 +1634,29 @@ export type IrrigationCCSystemConfigSetOptions = { @useSupervision() export class IrrigationCCSystemConfigSet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (IrrigationCCSystemConfigSetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.masterValveDelay = options.masterValveDelay; - this.highPressureThreshold = options.highPressureThreshold; - this.lowPressureThreshold = options.lowPressureThreshold; - this.rainSensorPolarity = options.rainSensorPolarity; - this.moistureSensorPolarity = options.moistureSensorPolarity; - } + super(options); + this.masterValveDelay = options.masterValveDelay; + this.highPressureThreshold = options.highPressureThreshold; + this.lowPressureThreshold = options.lowPressureThreshold; + this.rainSensorPolarity = options.rainSensorPolarity; + this.moistureSensorPolarity = options.moistureSensorPolarity; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCSystemConfigSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCSystemConfigSet({ + // nodeId: ctx.sourceNodeId, + // }); } public masterValveDelay: number; @@ -1565,7 +1665,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 +1682,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,45 +1704,81 @@ export class IrrigationCCSystemConfigSet extends IrrigationCC { ); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface IrrigationCCSystemConfigReportOptions { + masterValveDelay: number; + highPressureThreshold: number; + lowPressureThreshold: number; + rainSensorPolarity?: IrrigationSensorPolarity; + moistureSensorPolarity?: IrrigationSensorPolarity; +} + @CCCommand(IrrigationCommand.SystemConfigReport) export class IrrigationCCSystemConfigReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 2); - this.masterValveDelay = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.masterValveDelay = options.masterValveDelay; + this.highPressureThreshold = options.highPressureThreshold; + this.lowPressureThreshold = options.lowPressureThreshold; + this.rainSensorPolarity = options.rainSensorPolarity; + this.moistureSensorPolarity = options.moistureSensorPolarity; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCSystemConfigReport { + validatePayload(raw.payload.length >= 2); + const masterValveDelay = raw.payload[0]; let offset = 1; + let highPressureThreshold; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0 /* kPa */); - this.highPressureThreshold = value; + highPressureThreshold = value; offset += bytesRead; } + + let lowPressureThreshold; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0 /* kPa */); - this.lowPressureThreshold = value; + lowPressureThreshold = value; offset += bytesRead; } - validatePayload(this.payload.length >= offset + 1); - const polarity = this.payload[offset]; + + validatePayload(raw.payload.length >= offset + 1); + const polarity = raw.payload[offset]; + let rainSensorPolarity: IrrigationSensorPolarity | undefined; + let moistureSensorPolarity: IrrigationSensorPolarity | undefined; if (!!(polarity & 0b1000_0000)) { // The valid bit is set - this.rainSensorPolarity = polarity & 0b1; - this.moistureSensorPolarity = (polarity & 0b10) >>> 1; + rainSensorPolarity = polarity & 0b1; + moistureSensorPolarity = (polarity & 0b10) >>> 1; } + + return new IrrigationCCSystemConfigReport({ + nodeId: ctx.sourceNodeId, + masterValveDelay, + highPressureThreshold, + lowPressureThreshold, + rainSensorPolarity, + moistureSensorPolarity, + }); } @ccValue(IrrigationCCValues.masterValveDelay) @@ -1660,7 +1796,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 +1815,7 @@ export class IrrigationCCSystemConfigReport extends IrrigationCC { ); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1689,30 +1825,76 @@ export class IrrigationCCSystemConfigReport extends IrrigationCC { @expectedCCResponse(IrrigationCCSystemConfigReport) export class IrrigationCCSystemConfigGet extends IrrigationCC {} +// @publicAPI +export interface IrrigationCCValveInfoReportOptions { + valveId: ValveId; + connected: boolean; + nominalCurrent: number; + errorShortCircuit: boolean; + errorHighCurrent: boolean; + errorLowCurrent: boolean; + errorMaximumFlow?: boolean; + errorHighFlow?: boolean; + errorLowFlow?: boolean; +} + @CCCommand(IrrigationCommand.ValveInfoReport) export class IrrigationCCValveInfoReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 4); - if ((this.payload[0] & 0b1) === ValveType.MasterValve) { - this.valveId = "master"; + super(options); + + // TODO: Check implementation: + this.valveId = options.valveId; + this.connected = options.connected; + this.nominalCurrent = options.nominalCurrent; + this.errorShortCircuit = options.errorShortCircuit; + this.errorHighCurrent = options.errorHighCurrent; + this.errorLowCurrent = options.errorLowCurrent; + this.errorMaximumFlow = options.errorMaximumFlow; + this.errorHighFlow = options.errorHighFlow; + this.errorLowFlow = options.errorLowFlow; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCValveInfoReport { + validatePayload(raw.payload.length >= 4); + let valveId: ValveId; + if ((raw.payload[0] & 0b1) === ValveType.MasterValve) { + valveId = "master"; } else { - this.valveId = this.payload[1]; + valveId = raw.payload[1]; } - this.connected = !!(this.payload[0] & 0b10); - this.nominalCurrent = 10 * this.payload[2]; - this.errorShortCircuit = !!(this.payload[3] & 0b1); - this.errorHighCurrent = !!(this.payload[3] & 0b10); - this.errorLowCurrent = !!(this.payload[3] & 0b100); - if (this.valveId === "master") { - this.errorMaximumFlow = !!(this.payload[3] & 0b1000); - this.errorHighFlow = !!(this.payload[3] & 0b1_0000); - this.errorLowFlow = !!(this.payload[3] & 0b10_0000); + const connected = !!(raw.payload[0] & 0b10); + const nominalCurrent = 10 * raw.payload[2]; + const errorShortCircuit = !!(raw.payload[3] & 0b1); + const errorHighCurrent = !!(raw.payload[3] & 0b10); + const errorLowCurrent = !!(raw.payload[3] & 0b100); + let errorMaximumFlow: boolean | undefined; + let errorHighFlow: boolean | undefined; + let errorLowFlow: boolean | undefined; + if (valveId === "master") { + errorMaximumFlow = !!(raw.payload[3] & 0b1000); + errorHighFlow = !!(raw.payload[3] & 0b1_0000); + errorLowFlow = !!(raw.payload[3] & 0b10_0000); } + + return new IrrigationCCValveInfoReport({ + nodeId: ctx.sourceNodeId, + valveId, + connected, + nominalCurrent, + errorShortCircuit, + errorHighCurrent, + errorLowCurrent, + errorMaximumFlow, + errorHighFlow, + errorLowFlow, + }); } public readonly valveId: ValveId; @@ -1726,51 +1908,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 +1962,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,14 +1995,14 @@ export class IrrigationCCValveInfoReport extends IrrigationCC { message.errors = errors.map((e) => `\n· ${e}`).join(""); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface IrrigationCCValveInfoGetOptions extends CCCommandOptions { +export interface IrrigationCCValveInfoGetOptions { valveId: ValveId; } @@ -1842,36 +2024,40 @@ function testResponseForIrrigationCommandWithValveId( ) export class IrrigationCCValveInfoGet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveInfoGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.valveId = options.valveId; - } + super(options); + this.valveId = options.valveId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveInfoGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveInfoGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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,30 +2081,32 @@ export type IrrigationCCValveConfigSetOptions = { @useSupervision() export class IrrigationCCValveConfigSet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (IrrigationCCValveConfigSetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.valveId = options.valveId; - this.nominalCurrentHighThreshold = - options.nominalCurrentHighThreshold; - this.nominalCurrentLowThreshold = - options.nominalCurrentLowThreshold; - this.maximumFlow = options.maximumFlow; - this.highFlowThreshold = options.highFlowThreshold; - this.lowFlowThreshold = options.lowFlowThreshold; - this.useRainSensor = options.useRainSensor; - this.useMoistureSensor = options.useMoistureSensor; - } + super(options); + this.valveId = options.valveId; + this.nominalCurrentHighThreshold = options.nominalCurrentHighThreshold; + this.nominalCurrentLowThreshold = options.nominalCurrentLowThreshold; + this.maximumFlow = options.maximumFlow; + this.highFlowThreshold = options.highFlowThreshold; + this.lowFlowThreshold = options.lowFlowThreshold; + this.useRainSensor = options.useRainSensor; + this.useMoistureSensor = options.useMoistureSensor; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveConfigSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveConfigSet({ + // nodeId: ctx.sourceNodeId, + // }); } public valveId: ValveId; @@ -1930,7 +2118,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 +2134,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": @@ -1968,61 +2156,107 @@ export class IrrigationCCValveConfigSet extends IrrigationCC { } } +// @publicAPI +export interface IrrigationCCValveConfigReportOptions { + valveId: ValveId; + nominalCurrentHighThreshold: number; + nominalCurrentLowThreshold: number; + maximumFlow: number; + highFlowThreshold: number; + lowFlowThreshold: number; + useRainSensor: boolean; + useMoistureSensor: boolean; +} + @CCCommand(IrrigationCommand.ValveConfigReport) export class IrrigationCCValveConfigReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 4); - if ((this.payload[0] & 0b1) === ValveType.MasterValve) { - this.valveId = "master"; + super(options); + + // TODO: Check implementation: + this.valveId = options.valveId; + this.nominalCurrentHighThreshold = options.nominalCurrentHighThreshold; + this.nominalCurrentLowThreshold = options.nominalCurrentLowThreshold; + this.maximumFlow = options.maximumFlow; + this.highFlowThreshold = options.highFlowThreshold; + this.lowFlowThreshold = options.lowFlowThreshold; + this.useRainSensor = options.useRainSensor; + this.useMoistureSensor = options.useMoistureSensor; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCValveConfigReport { + validatePayload(raw.payload.length >= 4); + let valveId: ValveId; + if ((raw.payload[0] & 0b1) === ValveType.MasterValve) { + valveId = "master"; } else { - this.valveId = this.payload[1]; + valveId = raw.payload[1]; } - this.nominalCurrentHighThreshold = 10 * this.payload[2]; - this.nominalCurrentLowThreshold = 10 * this.payload[3]; + const nominalCurrentHighThreshold = 10 * raw.payload[2]; + const nominalCurrentLowThreshold = 10 * raw.payload[3]; let offset = 4; + let maximumFlow; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0 /* l/h */); - this.maximumFlow = value; + maximumFlow = value; offset += bytesRead; } + + let highFlowThreshold; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0 /* l/h */); - this.highFlowThreshold = value; + highFlowThreshold = value; offset += bytesRead; } + + let lowFlowThreshold; { const { value, scale, bytesRead } = parseFloatWithScale( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); validatePayload(scale === 0 /* l/h */); - this.lowFlowThreshold = value; + lowFlowThreshold = value; offset += bytesRead; } - validatePayload(this.payload.length >= offset + 1); - this.useRainSensor = !!(this.payload[offset] & 0b1); - this.useMoistureSensor = !!(this.payload[offset] & 0b10); + + validatePayload(raw.payload.length >= offset + 1); + const useRainSensor = !!(raw.payload[offset] & 0b1); + const useMoistureSensor = !!(raw.payload[offset] & 0b10); + + return new IrrigationCCValveConfigReport({ + nodeId: ctx.sourceNodeId, + valveId, + nominalCurrentHighThreshold, + nominalCurrentLowThreshold, + maximumFlow, + highFlowThreshold, + lowFlowThreshold, + useRainSensor, + useMoistureSensor, + }); } - 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 +2264,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 +2316,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": @@ -2102,7 +2336,7 @@ export class IrrigationCCValveConfigReport extends IrrigationCC { } // @publicAPI -export interface IrrigationCCValveConfigGetOptions extends CCCommandOptions { +export interface IrrigationCCValveConfigGetOptions { valveId: ValveId; } @@ -2113,36 +2347,40 @@ export interface IrrigationCCValveConfigGetOptions extends CCCommandOptions { ) export class IrrigationCCValveConfigGet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveConfigGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.valveId = options.valveId; - } + super(options); + this.valveId = options.valveId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveConfigGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveConfigGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, }, @@ -2151,7 +2389,7 @@ export class IrrigationCCValveConfigGet extends IrrigationCC { } // @publicAPI -export interface IrrigationCCValveRunOptions extends CCCommandOptions { +export interface IrrigationCCValveRunOptions { valveId: ValveId; duration: number; } @@ -2160,28 +2398,32 @@ export interface IrrigationCCValveRunOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveRun extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveRunOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.valveId = options.valveId; - this.duration = options.duration; - } + super(options); + this.valveId = options.valveId; + this.duration = options.duration; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveRun { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveRun({ + // nodeId: ctx.sourceNodeId, + // }); } 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 +2431,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,14 +2444,14 @@ export class IrrigationCCValveRun extends IrrigationCC { message.action = "turn off"; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface IrrigationCCValveTableSetOptions extends CCCommandOptions { +export interface IrrigationCCValveTableSetOptions { tableId: number; entries: ValveTableEntry[]; } @@ -2218,28 +2460,32 @@ export interface IrrigationCCValveTableSetOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveTableSet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveTableSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.tableId = options.tableId; - this.entries = options.entries; - } + super(options); + this.tableId = options.tableId; + this.entries = options.entries; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveTableSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveTableSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 +2494,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,34 +2511,55 @@ export class IrrigationCCValveTableSet extends IrrigationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface IrrigationCCValveTableReportOptions { + tableId: number; + entries: ValveTableEntry[]; +} + @CCCommand(IrrigationCommand.ValveTableReport) export class IrrigationCCValveTableReport extends IrrigationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload((this.payload.length - 1) % 3 === 0); - this.tableId = this.payload[0]; - this.entries = []; - for (let offset = 1; offset < this.payload.length; offset += 3) { - this.entries.push({ - valveId: this.payload[offset], - duration: this.payload.readUInt16BE(offset + 1), + super(options); + + // TODO: Check implementation: + this.tableId = options.tableId; + this.entries = options.entries; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): IrrigationCCValveTableReport { + validatePayload((raw.payload.length - 1) % 3 === 0); + const tableId = raw.payload[0]; + const entries: ValveTableEntry[] = []; + for (let offset = 1; offset < raw.payload.length; offset += 3) { + entries.push({ + valveId: raw.payload[offset], + duration: raw.payload.readUInt16BE(offset + 1), }); } + + return new IrrigationCCValveTableReport({ + nodeId: ctx.sourceNodeId, + tableId, + entries, + }); } 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,14 +2573,14 @@ export class IrrigationCCValveTableReport extends IrrigationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface IrrigationCCValveTableGetOptions extends CCCommandOptions { +export interface IrrigationCCValveTableGetOptions { tableId: number; } @@ -2331,33 +2598,37 @@ function testResponseForIrrigationValveTableGet( ) export class IrrigationCCValveTableGet extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveTableGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.tableId = options.tableId; - } + super(options); + this.tableId = options.tableId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveTableGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveTableGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, }, @@ -2366,7 +2637,7 @@ export class IrrigationCCValveTableGet extends IrrigationCC { } // @publicAPI -export interface IrrigationCCValveTableRunOptions extends CCCommandOptions { +export interface IrrigationCCValveTableRunOptions { tableIDs: number[]; } @@ -2374,39 +2645,43 @@ export interface IrrigationCCValveTableRunOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCValveTableRun extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCValveTableRunOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload + super(options); + this.tableIDs = options.tableIDs; + if (this.tableIDs.length < 1) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `${this.constructor.name}: At least one table ID must be specified.`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - this.tableIDs = options.tableIDs; - if (this.tableIDs.length < 1) { - throw new ZWaveError( - `${this.constructor.name}: At least one table ID must be specified.`, - ZWaveErrorCodes.Argument_Invalid, - ); - } } } + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCValveTableRun { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCValveTableRun({ + // nodeId: ctx.sourceNodeId, + // }); + } + 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")) @@ -2417,7 +2692,7 @@ export class IrrigationCCValveTableRun extends IrrigationCC { } // @publicAPI -export interface IrrigationCCSystemShutoffOptions extends CCCommandOptions { +export interface IrrigationCCSystemShutoffOptions { /** * The duration in minutes the system must stay off. * 255 or `undefined` will prevent schedules from running. @@ -2429,33 +2704,37 @@ export interface IrrigationCCSystemShutoffOptions extends CCCommandOptions { @useSupervision() export class IrrigationCCSystemShutoff extends IrrigationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | IrrigationCCSystemShutoffOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.duration = options.duration; - } + super(options); + this.duration = options.duration; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): IrrigationCCSystemShutoff { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new IrrigationCCSystemShutoff({ + // nodeId: ctx.sourceNodeId, + // }); } 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..87a9221149c6 100644 --- a/packages/cc/src/cc/LanguageCC.ts +++ b/packages/cc/src/cc/LanguageCC.ts @@ -2,6 +2,7 @@ import type { MessageOrCCLogEntry, MessageRecord, SupervisionResult, + WithAddress, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -13,18 +14,18 @@ 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"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -77,11 +78,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -97,13 +98,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, + endpointIndex: this.endpoint.index, language, country, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -113,33 +114,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 +154,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", }); @@ -158,7 +163,7 @@ export class LanguageCC extends CommandClass { } // @publicAPI -export interface LanguageCCSetOptions extends CCCommandOptions { +export interface LanguageCCSetOptions { language: string; country?: string; } @@ -167,21 +172,24 @@ export interface LanguageCCSetOptions extends CCCommandOptions { @useSupervision() export class LanguageCCSet extends LanguageCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | LanguageCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - // Populate properties from options object - this._language = options.language; - this._country = options.country; - } + super(options); + // Populate properties from options object + this._language = options.language; + this._country = options.country; + } + + public static from(_raw: CCRaw, _ctx: CCParsingContext): LanguageCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new LanguageCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } private _language: string; @@ -215,39 +223,54 @@ 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, }; } } +// @publicAPI +export interface LanguageCCReportOptions { + language: string; + country: MaybeNotKnown; +} + @CCCommand(LanguageCommand.Report) export class LanguageCCReport extends LanguageCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - // if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.language = this.payload.toString("ascii", 0, 3); - if (this.payload.length >= 5) { - this.country = this.payload.toString("ascii", 3, 5); + super(options); + this.language = options.language; + this.country = options.country; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): LanguageCCReport { + validatePayload(raw.payload.length >= 3); + const language = raw.payload.toString("ascii", 0, 3); + let country: MaybeNotKnown; + if (raw.payload.length >= 5) { + country = raw.payload.toString("ascii", 3, 5); } - // } + + return new LanguageCCReport({ + nodeId: ctx.sourceNodeId, + language, + country, + }); } @ccValue(LanguageCCValues.language) @@ -256,13 +279,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..98fd94d2dd33 100644 --- a/packages/cc/src/cc/LockCC.ts +++ b/packages/cc/src/cc/LockCC.ts @@ -5,15 +5,16 @@ import { MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, supervisedCommandSucceeded, 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 { @@ -27,10 +28,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -72,11 +73,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -91,12 +92,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, + endpointIndex: 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 +138,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", }); @@ -177,7 +182,7 @@ export class LockCC extends CommandClass { } // @publicAPI -export interface LockCCSetOptions extends CCCommandOptions { +export interface LockCCSetOptions { locked: boolean; } @@ -185,53 +190,71 @@ export interface LockCCSetOptions extends CCCommandOptions { @useSupervision() export class LockCCSet extends LockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | LockCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.locked = options.locked; - } + super(options); + this.locked = options.locked; + } + + public static from(_raw: CCRaw, _ctx: CCParsingContext): LockCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new LockCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 }, }; } } +// @publicAPI +export interface LockCCReportOptions { + locked: boolean; +} + @CCCommand(LockCommand.Report) export class LockCCReport extends LockCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.locked = this.payload[0] === 1; + super(options); + + // TODO: Check implementation: + this.locked = options.locked; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): LockCCReport { + validatePayload(raw.payload.length >= 1); + const locked = raw.payload[0] === 1; + + return new LockCCReport({ + nodeId: ctx.sourceNodeId, + locked, + }); } @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..c6d42b35f5ec 100644 --- a/packages/cc/src/cc/ManufacturerProprietaryCC.ts +++ b/packages/cc/src/cc/ManufacturerProprietaryCC.ts @@ -1,20 +1,18 @@ import { CommandClasses, - type IVirtualEndpoint, - type IZWaveEndpoint, + type WithAddress, ZWaveError, ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveApplicationHost, ZWaveHost } from "@zwave-js/host/safe"; -import { staticExtends } from "@zwave-js/shared/safe"; +import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; -import { CCAPI } from "../lib/API"; +import { CCAPI, type CCAPIEndpoint, type CCAPIHost } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -34,16 +32,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 +56,7 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { SpecificAPIConstructor != undefined && new.target !== SpecificAPIConstructor ) { - return new SpecificAPIConstructor(applHost, endpoint); + return new SpecificAPIConstructor(host, endpoint); } } } @@ -68,28 +66,28 @@ 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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, manufacturerId, unspecifiedExpectsResponse: true, }); cc.payload = data ?? Buffer.allocUnsafe(0); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerProprietaryCC >( cc, @@ -105,7 +103,7 @@ export class ManufacturerProprietaryCCAPI extends CCAPI { } // @publicAPI -export interface ManufacturerProprietaryCCOptions extends CCCommandOptions { +export interface ManufacturerProprietaryCCOptions { manufacturerId?: number; unspecifiedExpectsResponse?: boolean; } @@ -134,43 +132,39 @@ export class ManufacturerProprietaryCC extends CommandClass { declare ccCommand: undefined; public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ManufacturerProprietaryCCOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - // ManufacturerProprietaryCC has no CC command, so the first byte is stored in ccCommand. - this.manufacturerId = ((this.ccCommand as unknown as number) << 8) - + this.payload[0]; + this.manufacturerId = options.manufacturerId + ?? getManufacturerId(this); + this.unspecifiedExpectsResponse = options.unspecifiedExpectsResponse; - // Try to parse the proprietary command - const PCConstructor = getManufacturerProprietaryCCConstructor( - this.manufacturerId, - ); - if ( - PCConstructor - && new.target !== PCConstructor - && !staticExtends(new.target, PCConstructor) - ) { - return new PCConstructor(host, options); - } - - // If the constructor is correct, update the payload for subclass deserialization - this.payload = this.payload.subarray(1); - } else { - this.manufacturerId = options.manufacturerId - ?? getManufacturerId(this); - - this.unspecifiedExpectsResponse = - options.unspecifiedExpectsResponse; + // To use this CC, a manufacturer ID must exist in the value DB + // If it doesn't, the interview procedure will throw. + } - // To use this CC, a manufacturer ID must exist in the value DB - // If it doesn't, the interview procedure will throw. + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ManufacturerProprietaryCC { + validatePayload(raw.payload.length >= 1); + const manufacturerId = raw.payload.readUint16BE(0); + // Try to parse the proprietary command + const PCConstructor = getManufacturerProprietaryCCConstructor( + manufacturerId, + ); + if (PCConstructor) { + return PCConstructor.from( + raw.withPayload(raw.payload.subarray(2)), + ctx, + ); } + + return new ManufacturerProprietaryCC({ + nodeId: ctx.sourceNodeId, + manufacturerId, + }); } public manufacturerId?: number; @@ -192,7 +186,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 +199,7 @@ export class ManufacturerProprietaryCC extends CommandClass { ]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } public createSpecificInstance(): ManufacturerProprietaryCC | undefined { @@ -215,7 +209,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 +217,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 +239,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..a84795d789d5 100644 --- a/packages/cc/src/cc/ManufacturerSpecificCC.ts +++ b/packages/cc/src/cc/ManufacturerSpecificCC.ts @@ -1,4 +1,4 @@ -import type { MessageOrCCLogEntry } from "@zwave-js/core/safe"; +import type { MessageOrCCLogEntry, WithAddress } from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, @@ -9,18 +9,17 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -107,11 +106,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerSpecificCCReport >( cc, @@ -135,12 +134,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, + endpointIndex: this.endpoint.index, deviceIdType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ManufacturerSpecificCCDeviceSpecificReport >( cc, @@ -158,12 +157,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, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -178,23 +177,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 +205,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 +221,7 @@ export class ManufacturerSpecificCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -234,23 +235,30 @@ export interface ManufacturerSpecificCCReportOptions { @CCCommand(ManufacturerSpecificCommand.Report) export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, - options: - | (ManufacturerSpecificCCReportOptions & CCCommandOptions) - | CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 6); - this.manufacturerId = this.payload.readUInt16BE(0); - this.productType = this.payload.readUInt16BE(2); - this.productId = this.payload.readUInt16BE(4); - } else { - this.manufacturerId = options.manufacturerId; - this.productType = options.productType; - this.productId = options.productId; - } + super(options); + + this.manufacturerId = options.manufacturerId; + this.productType = options.productType; + this.productId = options.productId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ManufacturerSpecificCCReport { + validatePayload(raw.payload.length >= 6); + const manufacturerId = raw.payload.readUInt16BE(0); + const productType = raw.payload.readUInt16BE(2); + const productId = raw.payload.readUInt16BE(4); + + return new ManufacturerSpecificCCReport({ + nodeId: ctx.sourceNodeId, + manufacturerId, + productType, + productId, + }); } @ccValue(ManufacturerSpecificCCValues.manufacturerId) @@ -262,17 +270,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), @@ -286,26 +294,45 @@ export class ManufacturerSpecificCCReport extends ManufacturerSpecificCC { @expectedCCResponse(ManufacturerSpecificCCReport) export class ManufacturerSpecificCCGet extends ManufacturerSpecificCC {} +// @publicAPI +export interface ManufacturerSpecificCCDeviceSpecificReportOptions { + type: DeviceIdType; + deviceId: string; +} + @CCCommand(ManufacturerSpecificCommand.DeviceSpecificReport) export class ManufacturerSpecificCCDeviceSpecificReport extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); - this.type = this.payload[0] & 0b111; - const dataFormat = this.payload[1] >>> 5; - const dataLength = this.payload[1] & 0b11111; + // TODO: Check implementation: + this.type = options.type; + this.deviceId = options.deviceId; + } - validatePayload(dataLength > 0, this.payload.length >= 2 + dataLength); - const deviceIdData = this.payload.subarray(2, 2 + dataLength); - this.deviceId = dataFormat === 0 + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ManufacturerSpecificCCDeviceSpecificReport { + validatePayload(raw.payload.length >= 2); + const type: DeviceIdType = raw.payload[0] & 0b111; + const dataFormat = raw.payload[1] >>> 5; + const dataLength = raw.payload[1] & 0b11111; + validatePayload(dataLength > 0, raw.payload.length >= 2 + dataLength); + const deviceIdData = raw.payload.subarray(2, 2 + dataLength); + const deviceId: string = dataFormat === 0 ? deviceIdData.toString("utf8") : "0x" + deviceIdData.toString("hex"); + + return new ManufacturerSpecificCCDeviceSpecificReport({ + nodeId: ctx.sourceNodeId, + type, + deviceId, + }); } public readonly type: DeviceIdType; @@ -317,9 +344,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, @@ -329,9 +356,7 @@ export class ManufacturerSpecificCCDeviceSpecificReport } // @publicAPI -export interface ManufacturerSpecificCCDeviceSpecificGetOptions - extends CCCommandOptions -{ +export interface ManufacturerSpecificCCDeviceSpecificGetOptions { deviceIdType: DeviceIdType; } @@ -341,32 +366,36 @@ export class ManufacturerSpecificCCDeviceSpecificGet extends ManufacturerSpecificCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ManufacturerSpecificCCDeviceSpecificGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.deviceIdType = options.deviceIdType; - } + super(options); + this.deviceIdType = options.deviceIdType; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ManufacturerSpecificCCDeviceSpecificGet { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ManufacturerSpecificCCDeviceSpecificGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 9e75e708931f..b897946b8a7a 100644 --- a/packages/cc/src/cc/MeterCC.ts +++ b/packages/cc/src/cc/MeterCC.ts @@ -1,7 +1,7 @@ import { type FloatParameters, - type IZWaveEndpoint, type MaybeUnknown, + type WithAddress, encodeBitMask, encodeFloatWithScale, getFloatParameters, @@ -13,12 +13,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 +31,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 { type AllOrNone, @@ -52,10 +60,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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,68 +898,76 @@ export interface MeterCCReportOptions { @CCCommand(MeterCommand.Report) export class MeterCCReport extends MeterCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MeterCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - const { type, rateType, scale1, value, bytesRead } = - parseMeterValueAndInfo(this.payload, 0); - this.type = type; - this.rateType = rateType; - this.value = value; - let offset = bytesRead; - const floatSize = bytesRead - 2; - - if (this.payload.length >= offset + 2) { - this.deltaTime = this.payload.readUInt16BE(offset); - offset += 2; - if (this.deltaTime === 0xffff) { - this.deltaTime = UNKNOWN_STATE; - } + super(options); + + this.type = options.type; + this.scale = options.scale; + this.value = options.value; + this.previousValue = options.previousValue; + this.rateType = options.rateType ?? RateType.Unspecified; + this.deltaTime = options.deltaTime ?? UNKNOWN_STATE; + } - if ( - // Previous value is included only if delta time is not 0 - this.deltaTime !== 0 - && this.payload.length >= offset + floatSize - ) { - const { value: prevValue } = parseFloatWithScale( - // This float is split in the payload - Buffer.concat([ - Buffer.from([this.payload[1]]), - this.payload.subarray(offset), - ]), - ); - offset += floatSize; - this.previousValue = prevValue; - } - } else { - // 0 means that no previous value is included - this.deltaTime = 0; + public static from(raw: CCRaw, ctx: CCParsingContext): MeterCCReport { + const { type, rateType, scale1, value, bytesRead } = + parseMeterValueAndInfo(raw.payload, 0); + let offset = bytesRead; + const floatSize = bytesRead - 2; + let deltaTime: MaybeUnknown; + let previousValue: MaybeNotKnown; + + if (raw.payload.length >= offset + 2) { + deltaTime = raw.payload.readUInt16BE(offset); + offset += 2; + if (deltaTime === 0xffff) { + deltaTime = UNKNOWN_STATE; + } + + if ( + // Previous value is included only if delta time is not 0 + deltaTime !== 0 + && raw.payload.length >= offset + floatSize + ) { + const { value: prevValue } = parseFloatWithScale( + // This float is split in the payload + Buffer.concat([ + Buffer.from([raw.payload[1]]), + raw.payload.subarray(offset), + ]), + ); + offset += floatSize; + previousValue = prevValue; } - this.scale = parseScale(scale1, this.payload, offset); } else { - this.type = options.type; - this.scale = options.scale; - this.value = options.value; - this.previousValue = options.previousValue; - this.rateType = options.rateType ?? RateType.Unspecified; - this.deltaTime = options.deltaTime ?? UNKNOWN_STATE; + // 0 means that no previous value is included + deltaTime = 0; } + const scale = parseScale(scale1, raw.payload, offset); + + return new MeterCCReport({ + nodeId: ctx.sourceNodeId, + type, + rateType, + value, + deltaTime, + previousValue, + scale, + }); } - 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 +980,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 +992,7 @@ export class MeterCCReport extends MeterCC { } const supportedScales = this.getValue( - applHost, + ctx, MeterCCValues.supportedScales, ); if (supportedScales?.length) { @@ -968,7 +1002,7 @@ export class MeterCCReport extends MeterCC { } const supportedRateTypes = this.getValue( - applHost, + ctx, MeterCCValues.supportedRateTypes, ); if (supportedRateTypes?.length) { @@ -989,7 +1023,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.unit, @@ -999,7 +1033,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 +1045,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 +1084,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 +1104,7 @@ export class MeterCCReport extends MeterCC { message["prev. value"] = this.previousValue; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1095,45 +1129,52 @@ export interface MeterCCGetOptions { @expectedCCResponse(MeterCCReport, testResponseForMeterGet) export class MeterCCGet extends MeterCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MeterCCGetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length >= 1) { - this.rateType = (this.payload[0] & 0b11_000_000) >>> 6; - this.scale = (this.payload[0] & 0b00_111_000) >>> 3; - if (this.scale === 7) { - validatePayload(this.payload.length >= 2); - this.scale += this.payload[1]; - } + super(options); + this.rateType = options.rateType; + this.scale = options.scale; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): MeterCCGet { + let rateType: RateType | undefined; + let scale: number | undefined; + + if (raw.payload.length >= 1) { + rateType = (raw.payload[0] & 0b11_000_000) >>> 6; + scale = (raw.payload[0] & 0b00_111_000) >>> 3; + if (scale === 7) { + validatePayload(raw.payload.length >= 2); + scale += raw.payload[1]; } - } else { - this.rateType = options.rateType; - this.scale = options.scale; } + + return new MeterCCGet({ + nodeId: ctx.sourceNodeId, + rateType, + scale, + }); } 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 +1182,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 +1191,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 +1212,7 @@ export class MeterCCGet extends MeterCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1188,50 +1229,60 @@ export interface MeterCCSupportedReportOptions { @CCCommand(MeterCommand.SupportedReport) export class MeterCCSupportedReport extends MeterCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MeterCCSupportedReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.type = this.payload[0] & 0b0_00_11111; - this.supportsReset = !!(this.payload[0] & 0b1_00_00000); - const hasMoreScales = !!(this.payload[1] & 0b1_0000000); - if (hasMoreScales) { - // The bitmask is spread out - validatePayload(this.payload.length >= 3); - const extraBytes = this.payload[2]; - validatePayload(this.payload.length >= 3 + extraBytes); - // The bitmask is the original payload byte plus all following bytes - // Since the first byte only has 7 bits, we need to reduce all following bits by 1 - this.supportedScales = parseBitMask( - Buffer.concat([ - Buffer.from([this.payload[1] & 0b0_1111111]), - this.payload.subarray(3, 3 + extraBytes), - ]), - 0, - ).map((scale) => (scale >= 8 ? scale - 1 : scale)); - } else { - // only 7 bits in the bitmask. Bit 7 is 0, so no need to mask it out - this.supportedScales = parseBitMask( - Buffer.from([this.payload[1]]), - 0, - ); - } - // This is only present in V4+ - this.supportedRateTypes = parseBitMask( - Buffer.from([(this.payload[0] & 0b0_11_00000) >>> 5]), - 1, - ); + super(options); + + this.type = options.type; + this.supportsReset = options.supportsReset; + this.supportedScales = options.supportedScales; + this.supportedRateTypes = options.supportedRateTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MeterCCSupportedReport { + validatePayload(raw.payload.length >= 2); + const type = raw.payload[0] & 0b0_00_11111; + const supportsReset = !!(raw.payload[0] & 0b1_00_00000); + const hasMoreScales = !!(raw.payload[1] & 0b1_0000000); + + let supportedScales: number[] | undefined; + if (hasMoreScales) { + // The bitmask is spread out + validatePayload(raw.payload.length >= 3); + const extraBytes = raw.payload[2]; + validatePayload(raw.payload.length >= 3 + extraBytes); + // The bitmask is the original payload byte plus all following bytes + // Since the first byte only has 7 bits, we need to reduce all following bits by 1 + supportedScales = parseBitMask( + Buffer.concat([ + Buffer.from([raw.payload[1] & 0b0_1111111]), + raw.payload.subarray(3, 3 + extraBytes), + ]), + 0, + ).map((scale) => (scale >= 8 ? scale - 1 : scale)); } else { - this.type = options.type; - this.supportsReset = options.supportsReset; - this.supportedScales = options.supportedScales; - this.supportedRateTypes = options.supportedRateTypes; + // only 7 bits in the bitmask. Bit 7 is 0, so no need to mask it out + supportedScales = parseBitMask( + Buffer.from([raw.payload[1]]), + 0, + ); } + // This is only present in V4+ + const supportedRateTypes: RateType[] = parseBitMask( + Buffer.from([(raw.payload[0] & 0b0_11_00000) >>> 5]), + 1, + ); + + return new MeterCCSupportedReport({ + nodeId: ctx.sourceNodeId, + type, + supportsReset, + supportedScales, + supportedRateTypes, + }); } @ccValue(MeterCCValues.type) @@ -1246,13 +1297,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 +1317,7 @@ export class MeterCCSupportedReport extends MeterCC { rateType, scale, ); - this.ensureMetadata(applHost, resetSingleValue, { + this.ensureMetadata(ctx, resetSingleValue, { ...resetSingleValue.meta, label: `Reset ${ getValueLabel( @@ -1280,7 +1333,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 +1365,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 +1385,7 @@ export class MeterCCSupportedReport extends MeterCC { .join(", "), }; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1354,32 +1407,38 @@ export type MeterCCResetOptions = AllOrNone<{ @useSupervision() export class MeterCCReset extends MeterCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MeterCCResetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length > 0) { - const { - type, - rateType, - scale1, - value, - bytesRead: scale2Offset, - } = parseMeterValueAndInfo(this.payload, 0); - this.type = type; - this.rateType = rateType; - this.targetValue = value; - this.scale = parseScale(scale1, this.payload, scale2Offset); - } - } else { - this.type = options.type; - this.scale = options.scale; - this.rateType = options.rateType; - this.targetValue = options.targetValue; + super(options); + this.type = options.type; + this.scale = options.scale; + this.rateType = options.rateType; + this.targetValue = options.targetValue; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): MeterCCReset { + if (raw.payload.length === 0) { + return new MeterCCReset({ + nodeId: ctx.sourceNodeId, + }); } + + const { + type, + rateType, + scale1, + value: targetValue, + bytesRead: scale2Offset, + } = parseMeterValueAndInfo(raw.payload, 0); + const scale = parseScale(scale1, raw.payload, scale2Offset); + + return new MeterCCReset({ + nodeId: ctx.sourceNodeId, + type, + rateType, + targetValue, + scale, + }); } public type: number | undefined; @@ -1387,7 +1446,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 +1469,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 +1488,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..b6ed2b7fac76 100644 --- a/packages/cc/src/cc/MultiChannelAssociationCC.ts +++ b/packages/cc/src/cc/MultiChannelAssociationCC.ts @@ -1,7 +1,8 @@ import type { - IZWaveEndpoint, + EndpointId, MessageRecord, SupervisionResult, + WithAddress, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -16,18 +17,18 @@ 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"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -202,14 +203,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, + endpointIndex: this.endpoint.index, + }); + const response = await this.host.sendCommand< MultiChannelAssociationCCSupportedGroupingsReport >( cc, @@ -225,15 +223,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, + endpointIndex: this.endpoint.index, + groupCount, + }); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -247,12 +242,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, + endpointIndex: this.endpoint.index, groupId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelAssociationCCReport >( cc, @@ -272,12 +267,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, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -292,12 +287,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } /** @@ -317,13 +312,13 @@ 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, + endpointIndex: this.endpoint.index, groupId: group, nodeIds: destinations .filter((d) => d.endpoint != undefined) @@ -334,15 +329,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } } @@ -370,18 +370,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 +387,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 +405,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 +458,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 +500,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 +566,7 @@ currently assigned endpoints: ${ }) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -575,7 +575,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 +586,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 +597,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,44 +622,52 @@ export type MultiChannelAssociationCCSetOptions = @useSupervision() export class MultiChannelAssociationCCSet extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MultiChannelAssociationCCSetOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupId = this.payload[0]; - ({ nodeIds: this.nodeIds, endpoints: this.endpoints } = - deserializeMultiChannelAssociationDestination( - this.payload.subarray(1), - )); - } else { - if (options.groupId < 1) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.groupId = options.groupId; - this.nodeIds = ("nodeIds" in options && options.nodeIds) || []; - if (this.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { - throw new ZWaveError( - `All node IDs must be between 1 and ${MAX_NODES}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.endpoints = ("endpoints" in options && options.endpoints) - || []; + super(options); + if (options.groupId < 1) { + throw new ZWaveError( + "The group id must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.groupId = options.groupId; + this.nodeIds = ("nodeIds" in options && options.nodeIds) || []; + if (this.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { + throw new ZWaveError( + `All node IDs must be between 1 and ${MAX_NODES}!`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.endpoints = ("endpoints" in options && options.endpoints) + || []; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelAssociationCCSet { + validatePayload(raw.payload.length >= 1); + const groupId = raw.payload[0]; + + const { nodeIds, endpoints } = + deserializeMultiChannelAssociationDestination( + raw.payload.subarray(1), + ); + + return new MultiChannelAssociationCCSet({ + nodeId: ctx.sourceNodeId, + groupId, + nodeIds, + endpoints, + }); } public groupId: number; 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 +675,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,49 +704,41 @@ export interface MultiChannelAssociationCCRemoveOptions { @useSupervision() export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MultiChannelAssociationCCRemoveOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupId = this.payload[0]; - ({ nodeIds: this.nodeIds, endpoints: this.endpoints } = - deserializeMultiChannelAssociationDestination( - 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, - ); - } + super(options); + // When removing associations, we allow invalid node IDs. + // See GH#3606 - it is possible that those exist. + this.groupId = options.groupId; + this.nodeIds = options.nodeIds; + this.endpoints = options.endpoints; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelAssociationCCRemove { + validatePayload(raw.payload.length >= 1); + const groupId: number | undefined = raw.payload[0]; + + const { nodeIds, endpoints } = + deserializeMultiChannelAssociationDestination( + raw.payload.subarray(1), + ); - // When removing associations, we allow invalid node IDs. - // See GH#3606 - it is possible that those exist. - this.groupId = options.groupId; - this.nodeIds = options.nodeIds; - this.endpoints = options.endpoints; - } + return new MultiChannelAssociationCCRemove({ + nodeId: ctx.sourceNodeId, + groupId, + nodeIds, + endpoints, + }); } public groupId?: number; 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 +746,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 +760,7 @@ export class MultiChannelAssociationCCRemove extends MultiChannelAssociationCC { message.endpoints = endpointAddressesToString(this.endpoints); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -778,29 +778,39 @@ export interface MultiChannelAssociationCCReportOptions { @CCCommand(MultiChannelAssociationCommand.Report) export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (MultiChannelAssociationCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.groupId = this.payload[0]; - this.maxNodes = this.payload[1]; - this.reportsToFollow = this.payload[2]; - ({ nodeIds: this.nodeIds, endpoints: this.endpoints } = - deserializeMultiChannelAssociationDestination( - this.payload.subarray(3), - )); - } else { - this.groupId = options.groupId; - this.maxNodes = options.maxNodes; - this.nodeIds = options.nodeIds; - this.endpoints = options.endpoints; - this.reportsToFollow = options.reportsToFollow; - } + super(options); + + this.groupId = options.groupId; + this.maxNodes = options.maxNodes; + this.nodeIds = options.nodeIds; + this.endpoints = options.endpoints; + this.reportsToFollow = options.reportsToFollow; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelAssociationCCReport { + validatePayload(raw.payload.length >= 3); + const groupId = raw.payload[0]; + const maxNodes = raw.payload[1]; + const reportsToFollow = raw.payload[2]; + + const { nodeIds, endpoints } = + deserializeMultiChannelAssociationDestination( + raw.payload.subarray(3), + ); + + return new MultiChannelAssociationCCReport({ + nodeId: ctx.sourceNodeId, + groupId, + maxNodes, + nodeIds, + endpoints, + reportsToFollow, + }); } public readonly groupId: number; @@ -835,8 +845,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 +858,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 +871,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, @@ -878,7 +888,7 @@ export class MultiChannelAssociationCCReport extends MultiChannelAssociationCC { } // @publicAPI -export interface MultiChannelAssociationCCGetOptions extends CCCommandOptions { +export interface MultiChannelAssociationCCGetOptions { groupId: number; } @@ -886,45 +896,48 @@ export interface MultiChannelAssociationCCGetOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelAssociationCCReport) export class MultiChannelAssociationCCGet extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelAssociationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupId = this.payload[0]; - } else { - if (options.groupId < 1) { - throw new ZWaveError( - "The group id must be positive!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.groupId = options.groupId; + super(options); + if (options.groupId < 1) { + throw new ZWaveError( + "The group id must be positive!", + ZWaveErrorCodes.Argument_Invalid, + ); } + this.groupId = options.groupId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelAssociationCCGet { + validatePayload(raw.payload.length >= 1); + const groupId = raw.payload[0]; + + return new MultiChannelAssociationCCGet({ + nodeId: ctx.sourceNodeId, + groupId, + }); } 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 }, }; } } // @publicAPI -export interface MultiChannelAssociationCCSupportedGroupingsReportOptions - extends CCCommandOptions -{ +export interface MultiChannelAssociationCCSupportedGroupingsReportOptions { groupCount: number; } @@ -933,32 +946,39 @@ export class MultiChannelAssociationCCSupportedGroupingsReport extends MultiChannelAssociationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelAssociationCCSupportedGroupingsReportOptions, + options: WithAddress< + MultiChannelAssociationCCSupportedGroupingsReportOptions + >, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.groupCount = this.payload[0]; - } else { - this.groupCount = options.groupCount; - } + this.groupCount = options.groupCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelAssociationCCSupportedGroupingsReport { + validatePayload(raw.payload.length >= 1); + const groupCount = raw.payload[0]; + + return new MultiChannelAssociationCCSupportedGroupingsReport({ + nodeId: ctx.sourceNodeId, + groupCount, + }); } @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..331d7db88f80 100644 --- a/packages/cc/src/cc/MultiChannelCC.ts +++ b/packages/cc/src/cc/MultiChannelCC.ts @@ -2,12 +2,12 @@ import { type ApplicationNodeInformation, CommandClasses, type GenericDeviceClass, - type IZWaveNode, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, type SpecificDeviceClass, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeApplicationNodeInformation, @@ -20,18 +20,19 @@ 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"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -42,6 +43,10 @@ import { expectedCCResponse, implementedVersion, } from "../lib/CommandClassDecorators"; +import { + isEncapsulatingCommandClass, + isMultiEncapsulatingCommandClass, +} from "../lib/EncapsulatingCommandClass"; import { V } from "../lib/Values"; import { MultiChannelCommand } from "../lib/_Types"; @@ -129,8 +134,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 +149,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 +219,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCEndPointReport >( cc, @@ -243,12 +248,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, + endpointIndex: this.endpoint.index, requestedEndpoint: endpoint, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCCapabilityReport >( cc, @@ -282,13 +287,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, + endpointIndex: this.endpoint.index, genericClass, specificClass, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCEndPointFindReport >( cc, @@ -306,12 +311,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, + endpointIndex: this.endpoint.index, requestedEndpoint: endpoint, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultiChannelCCAggregatedMembersReport >( cc, @@ -324,21 +329,18 @@ export class MultiChannelCCAPI extends CCAPI { // want to pay the cost of validating each call // eslint-disable-next-line @zwave-js/ccapi-validate-args public async sendEncapsulated( - options: Omit< - MultiChannelCCCommandEncapsulationOptions, - keyof CCCommandOptions - >, + options: MultiChannelCCCommandEncapsulationOptions, ): Promise { this.assertSupportsCommand( MultiChannelCommand, 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,12 +801,12 @@ supported CCs:`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } // @publicAPI -export interface MultiChannelCCEndPointReportOptions extends CCCommandOptions { +export interface MultiChannelCCEndPointReportOptions { countIsDynamic: boolean; identicalCapabilities: boolean; individualCount: number; @@ -817,27 +816,37 @@ export interface MultiChannelCCEndPointReportOptions extends CCCommandOptions { @CCCommand(MultiChannelCommand.EndPointReport) export class MultiChannelCCEndPointReport extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCEndPointReportOptions, + options: WithAddress, ) { - super(host, 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) { - this.aggregatedCount = this.payload[2] & 0b01111111; - } - } else { - this.countIsDynamic = options.countIsDynamic; - this.identicalCapabilities = options.identicalCapabilities; - this.individualCount = options.individualCount; - this.aggregatedCount = options.aggregatedCount; + super(options); + + this.countIsDynamic = options.countIsDynamic; + this.identicalCapabilities = options.identicalCapabilities; + this.individualCount = options.individualCount; + this.aggregatedCount = options.aggregatedCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCEndPointReport { + validatePayload(raw.payload.length >= 2); + const countIsDynamic = !!(raw.payload[0] & 0b10000000); + const identicalCapabilities = !!(raw.payload[0] & 0b01000000); + const individualCount = raw.payload[1] & 0b01111111; + let aggregatedCount: MaybeNotKnown; + + if (raw.payload.length >= 3) { + aggregatedCount = raw.payload[2] & 0b01111111; } + + return new MultiChannelCCEndPointReport({ + nodeId: ctx.sourceNodeId, + countIsDynamic, + identicalCapabilities, + individualCount, + aggregatedCount, + }); } @ccValue(MultiChannelCCValues.endpointCountIsDynamic) @@ -852,17 +861,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 +881,7 @@ export class MultiChannelCCEndPointReport extends MultiChannelCC { message["endpoint count (aggregated)"] = this.aggregatedCount; } const ret = { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; return ret; @@ -884,9 +893,7 @@ export class MultiChannelCCEndPointReport extends MultiChannelCC { export class MultiChannelCCEndPointGet extends MultiChannelCC {} // @publicAPI -export interface MultiChannelCCCapabilityReportOptions - extends CCCommandOptions -{ +export interface MultiChannelCCCapabilityReportOptions { endpointIndex: number; genericDeviceClass: number; specificDeviceClass: number; @@ -900,56 +907,65 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC implements ApplicationNodeInformation { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCCapabilityReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // Only validate the bytes we expect to see here - // parseApplicationNodeInformation does its own validation - validatePayload(this.payload.length >= 1); - this.endpointIndex = this.payload[0] & 0b01111111; - this.isDynamic = !!(this.payload[0] & 0b10000000); + super(options); + + this.endpointIndex = options.endpointIndex; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.supportedCCs = options.supportedCCs; + this.isDynamic = options.isDynamic; + this.wasRemoved = options.wasRemoved; + } - const NIF = parseApplicationNodeInformation( - this.payload.subarray(1), - ); - this.genericDeviceClass = NIF.genericDeviceClass; - this.specificDeviceClass = NIF.specificDeviceClass; - this.supportedCCs = NIF.supportedCCs; - - // Removal reports have very specific information - this.wasRemoved = this.isDynamic - && this.genericDeviceClass === 0xff // "Non-Interoperable" - && this.specificDeviceClass === 0x00; - } else { - this.endpointIndex = options.endpointIndex; - this.genericDeviceClass = options.genericDeviceClass; - this.specificDeviceClass = options.specificDeviceClass; - this.supportedCCs = options.supportedCCs; - this.isDynamic = options.isDynamic; - this.wasRemoved = options.wasRemoved; - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCCapabilityReport { + // Only validate the bytes we expect to see here + // parseApplicationNodeInformation does its own validation + validatePayload(raw.payload.length >= 1); + const endpointIndex = raw.payload[0] & 0b01111111; + const isDynamic = !!(raw.payload[0] & 0b10000000); + const NIF = parseApplicationNodeInformation( + raw.payload.subarray(1), + ); + const genericDeviceClass = NIF.genericDeviceClass; + const specificDeviceClass = NIF.specificDeviceClass; + const supportedCCs: CommandClasses[] = NIF.supportedCCs; + + // Removal reports have very specific information + const wasRemoved: boolean = isDynamic + && genericDeviceClass === 0xff // "Non-Interoperable" + && specificDeviceClass === 0x00; + + return new MultiChannelCCCapabilityReport({ + nodeId: ctx.sourceNodeId, + endpointIndex, + isDynamic, + genericDeviceClass, + specificDeviceClass, + supportedCCs, + wasRemoved, + }); } - 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 +977,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 +985,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( @@ -994,7 +1010,7 @@ export class MultiChannelCCCapabilityReport extends MultiChannelCC } // @publicAPI -export interface MultiChannelCCCapabilityGetOptions extends CCCommandOptions { +export interface MultiChannelCCCapabilityGetOptions { requestedEndpoint: number; } @@ -1012,39 +1028,42 @@ function testResponseForMultiChannelCapabilityGet( ) export class MultiChannelCCCapabilityGet extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCCapabilityGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.requestedEndpoint = this.payload[0] & 0b01111111; - } else { - this.requestedEndpoint = options.requestedEndpoint; - } + super(options); + this.requestedEndpoint = options.requestedEndpoint; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCCapabilityGet { + validatePayload(raw.payload.length >= 1); + const requestedEndpoint = raw.payload[0] & 0b01111111; + + return new MultiChannelCCCapabilityGet({ + nodeId: ctx.sourceNodeId, + requestedEndpoint, + }); } 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 }, }; } } // @publicAPI -export interface MultiChannelCCEndPointFindReportOptions - extends CCCommandOptions -{ +export interface MultiChannelCCEndPointFindReportOptions { genericClass: number; specificClass: number; foundEndpoints: number[]; @@ -1054,30 +1073,38 @@ export interface MultiChannelCCEndPointFindReportOptions @CCCommand(MultiChannelCommand.EndPointFindReport) export class MultiChannelCCEndPointFindReport extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCEndPointFindReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.reportsToFollow = this.payload[0]; - this.genericClass = this.payload[1]; - this.specificClass = this.payload[2]; - - // Some devices omit the endpoint list although that is not allowed in the specs - // therefore don't validatePayload here. - this.foundEndpoints = [...this.payload.subarray(3)] - .map((e) => e & 0b01111111) - .filter((e) => e !== 0); - } else { - this.genericClass = options.genericClass; - this.specificClass = options.specificClass; - this.foundEndpoints = options.foundEndpoints; - this.reportsToFollow = options.reportsToFollow; - } + super(options); + + this.genericClass = options.genericClass; + this.specificClass = options.specificClass; + this.foundEndpoints = options.foundEndpoints; + this.reportsToFollow = options.reportsToFollow; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCEndPointFindReport { + validatePayload(raw.payload.length >= 3); + const reportsToFollow = raw.payload[0]; + const genericClass = raw.payload[1]; + const specificClass = raw.payload[2]; + + // Some devices omit the endpoint list although that is not allowed in the specs + // therefore don't validatePayload here. + const foundEndpoints = [...raw.payload.subarray(3)] + .map((e) => e & 0b01111111) + .filter((e) => e !== 0); + + return new MultiChannelCCEndPointFindReport({ + nodeId: ctx.sourceNodeId, + reportsToFollow, + genericClass, + specificClass, + foundEndpoints, + }); } public genericClass: number; @@ -1085,7 +1112,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 +1121,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 +1137,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 +1146,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, @@ -1138,7 +1165,7 @@ export class MultiChannelCCEndPointFindReport extends MultiChannelCC { } // @publicAPI -export interface MultiChannelCCEndPointFindOptions extends CCCommandOptions { +export interface MultiChannelCCEndPointFindOptions { genericClass: number; specificClass: number; } @@ -1147,33 +1174,39 @@ export interface MultiChannelCCEndPointFindOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelCCEndPointFindReport) export class MultiChannelCCEndPointFind extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCEndPointFindOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.genericClass = this.payload[0]; - this.specificClass = this.payload[1]; - } else { - this.genericClass = options.genericClass; - this.specificClass = options.specificClass; - } + super(options); + this.genericClass = options.genericClass; + this.specificClass = options.specificClass; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCEndPointFind { + validatePayload(raw.payload.length >= 2); + const genericClass = raw.payload[0]; + const specificClass = raw.payload[1]; + + return new MultiChannelCCEndPointFind({ + nodeId: ctx.sourceNodeId, + genericClass, + specificClass, + }); } 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, @@ -1186,20 +1219,40 @@ export class MultiChannelCCEndPointFind extends MultiChannelCC { } } +// @publicAPI +export interface MultiChannelCCAggregatedMembersReportOptions { + aggregatedEndpointIndex: number; + members: number[]; +} + @CCCommand(MultiChannelCommand.AggregatedMembersReport) export class MultiChannelCCAggregatedMembersReport extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); - this.aggregatedEndpointIndex = this.payload[0] & 0b0111_1111; - const bitMaskLength = this.payload[1]; - validatePayload(this.payload.length >= 2 + bitMaskLength); - const bitMask = this.payload.subarray(2, 2 + bitMaskLength); - this.members = parseBitMask(bitMask); + // TODO: Check implementation: + this.aggregatedEndpointIndex = options.aggregatedEndpointIndex; + this.members = options.members; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCAggregatedMembersReport { + validatePayload(raw.payload.length >= 2); + const aggregatedEndpointIndex = raw.payload[0] & 0b0111_1111; + const bitMaskLength = raw.payload[1]; + validatePayload(raw.payload.length >= 2 + bitMaskLength); + const bitMask = raw.payload.subarray(2, 2 + bitMaskLength); + const members = parseBitMask(bitMask); + + return new MultiChannelCCAggregatedMembersReport({ + nodeId: ctx.sourceNodeId, + aggregatedEndpointIndex, + members, + }); } public readonly aggregatedEndpointIndex: number; @@ -1211,9 +1264,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(", "), @@ -1223,9 +1276,7 @@ export class MultiChannelCCAggregatedMembersReport extends MultiChannelCC { } // @publicAPI -export interface MultiChannelCCAggregatedMembersGetOptions - extends CCCommandOptions -{ +export interface MultiChannelCCAggregatedMembersGetOptions { requestedEndpoint: number; } @@ -1233,33 +1284,37 @@ export interface MultiChannelCCAggregatedMembersGetOptions @expectedCCResponse(MultiChannelCCAggregatedMembersReport) export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCAggregatedMembersGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.requestedEndpoint = options.requestedEndpoint; - } + super(options); + this.requestedEndpoint = options.requestedEndpoint; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): MultiChannelCCAggregatedMembersGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new MultiChannelCCAggregatedMembersGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 }, }; } @@ -1268,9 +1323,7 @@ export class MultiChannelCCAggregatedMembersGet extends MultiChannelCC { type MultiChannelCCDestination = number | (1 | 2 | 3 | 4 | 5 | 6 | 7)[]; // @publicAPI -export interface MultiChannelCCCommandEncapsulationOptions - extends CCCommandOptions -{ +export interface MultiChannelCCCommandEncapsulationOptions { encapsulated: CommandClass; destination: MultiChannelCCDestination; } @@ -1319,64 +1372,81 @@ function testResponseForCommandEncapsulation( ) export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCCommandEncapsulationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - if ( - this.host.getDeviceConfig?.(this.nodeId as number)?.compat - ?.treatDestinationEndpointAsSource - ) { - // This device incorrectly uses the destination field to indicate the source endpoint - this.endpointIndex = this.payload[1] & 0b0111_1111; - this.destination = 0; - } else { - // Parse normally - this.endpointIndex = this.payload[0] & 0b0111_1111; - const isBitMask = !!(this.payload[1] & 0b1000_0000); - const destination = this.payload[1] & 0b0111_1111; - if (isBitMask) { - this.destination = parseBitMask( - Buffer.from([destination]), - ) as any; - } else { - this.destination = destination; + super(options); + this.encapsulated = options.encapsulated; + this.encapsulated.encapsulatingCC = this as any; + // Propagate the endpoint index all the way down + let cur: CommandClass = this; + while (cur) { + if (isMultiEncapsulatingCommandClass(cur)) { + for (const cc of cur.encapsulated) { + cc.endpointIndex = this.endpointIndex; } + break; + } else if (isEncapsulatingCommandClass(cur)) { + cur.encapsulated.endpointIndex = this.endpointIndex; + cur = cur.encapsulated; + } else { + break; } - // No need to validate further, each CC does it for itself - this.encapsulated = CommandClass.from(this.host, { - data: this.payload.subarray(2), - fromEncapsulation: true, - encapCC: this, - origin: options.origin, - frameType: options.frameType, - }); - } else { - this.encapsulated = options.encapsulated; - options.encapsulated.encapsulatingCC = this as any; - this.destination = options.destination; + } + 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; - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCCommandEncapsulation { + validatePayload(raw.payload.length >= 2); + + let endpointIndex: number; + let destination: MultiChannelCCDestination; + + if ( + ctx.getDeviceConfig?.(ctx.sourceNodeId) + ?.compat?.treatDestinationEndpointAsSource + ) { + // This device incorrectly uses the destination field to indicate the source endpoint + endpointIndex = raw.payload[1] & 0b0111_1111; + destination = 0; + } else { + // Parse normally + endpointIndex = raw.payload[0] & 0b0111_1111; + const isBitMask = !!(raw.payload[1] & 0b1000_0000); + destination = raw.payload[1] & 0b0111_1111; + if (isBitMask) { + destination = parseBitMask( + Buffer.from([destination]), + ) as any; } } + // No need to validate further, each CC does it for itself + const encapsulated = CommandClass.parse(raw.payload.subarray(2), ctx); + return new MultiChannelCCCommandEncapsulation({ + nodeId: ctx.sourceNodeId, + endpointIndex, + destination, + encapsulated, + }); } public encapsulated: CommandClass; /** 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 +1454,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" @@ -1407,25 +1477,46 @@ export class MultiChannelCCCommandEncapsulation extends MultiChannelCC { } } +// @publicAPI +export interface MultiChannelCCV1ReportOptions { + requestedCC: CommandClasses; + endpointCount: number; +} + @CCCommand(MultiChannelCommand.ReportV1) export class MultiChannelCCV1Report extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.requestedCC = options.requestedCC; + this.endpointCount = options.endpointCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCV1Report { // V1 won't be extended in the future, so do an exact check - validatePayload(this.payload.length === 2); - this.requestedCC = this.payload[0]; - this.endpointCount = this.payload[1]; + validatePayload(raw.payload.length === 2); + const requestedCC: CommandClasses = raw.payload[0]; + const endpointCount = raw.payload[1]; + + return new MultiChannelCCV1Report({ + nodeId: ctx.sourceNodeId, + requestedCC, + endpointCount, + }); } 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, @@ -1442,7 +1533,7 @@ function testResponseForMultiChannelV1Get( } // @publicAPI -export interface MultiChannelCCV1GetOptions extends CCCommandOptions { +export interface MultiChannelCCV1GetOptions { requestedCC: CommandClasses; } @@ -1450,33 +1541,37 @@ export interface MultiChannelCCV1GetOptions extends CCCommandOptions { @expectedCCResponse(MultiChannelCCV1Report, testResponseForMultiChannelV1Get) export class MultiChannelCCV1Get extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCV1GetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.requestedCC = options.requestedCC; - } + super(options); + this.requestedCC = options.requestedCC; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): MultiChannelCCV1Get { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new MultiChannelCCV1Get({ + // nodeId: ctx.sourceNodeId, + // }); } 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) }, }; } @@ -1501,9 +1596,7 @@ function testResponseForV1CommandEncapsulation( } // @publicAPI -export interface MultiChannelCCV1CommandEncapsulationOptions - extends CCCommandOptions -{ +export interface MultiChannelCCV1CommandEncapsulationOptions { encapsulated: CommandClass; } @@ -1514,44 +1607,47 @@ export interface MultiChannelCCV1CommandEncapsulationOptions ) export class MultiChannelCCV1CommandEncapsulation extends MultiChannelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultiChannelCCV1CommandEncapsulationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.endpointIndex = this.payload[0]; - - // Some devices send invalid reports, i.e. MultiChannelCCV1CommandEncapsulation, but with V2+ binary format - // This would be a NoOp CC, but it makes no sense to encapsulate that. - const isV2withV1Header = this.payload.length >= 2 - && this.payload[1] === 0x00; - - // No need to validate further, each CC does it for itself - this.encapsulated = CommandClass.from(this.host, { - data: this.payload.subarray(isV2withV1Header ? 2 : 1), - fromEncapsulation: true, - encapCC: this, - origin: options.origin, - frameType: options.frameType, - }); - } else { - this.encapsulated = options.encapsulated; - // No need to distinguish between source and destination in V1 - this.endpointIndex = this.encapsulated.endpointIndex; - } + super(options); + this.encapsulated = options.encapsulated; + // No need to distinguish between source and destination in V1 + this.endpointIndex = this.encapsulated.endpointIndex; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiChannelCCV1CommandEncapsulation { + validatePayload(raw.payload.length >= 1); + const endpointIndex = raw.payload[0]; + + // Some devices send invalid reports, i.e. MultiChannelCCV1CommandEncapsulation, but with V2+ binary format + // This would be a NoOp CC, but it makes no sense to encapsulate that. + const isV2withV1Header = raw.payload.length >= 2 + && raw.payload[1] === 0x00; + + // No need to validate further, each CC does it for itself + const encapsulated = CommandClass.parse( + raw.payload.subarray(isV2withV1Header ? 2 : 1), + ctx, + ); + + return new MultiChannelCCV1CommandEncapsulation({ + nodeId: ctx.sourceNodeId, + endpointIndex, + encapsulated, + }); } 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 +1655,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..adecb65a6f01 100644 --- a/packages/cc/src/cc/MultiCommandCC.ts +++ b/packages/cc/src/cc/MultiCommandCC.ts @@ -1,19 +1,19 @@ -import type { MessageOrCCLogEntry } from "@zwave-js/core/safe"; import { CommandClasses, EncapsulationFlags, type MaybeNotKnown, + type MessageOrCCLogEntry, + type WithAddress, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { + CCEncodingContext, + CCParsingContext, + GetValueDB, +} from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -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, }); @@ -95,9 +94,7 @@ export class MultiCommandCC extends CommandClass { } // @publicAPI -export interface MultiCommandCCCommandEncapsulationOptions - extends CCCommandOptions -{ +export interface MultiCommandCCCommandEncapsulationOptions { encapsulated: CommandClass[]; } @@ -105,60 +102,64 @@ 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, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const numCommands = this.payload[0]; - this.encapsulated = []; - let offset = 1; - for (let i = 0; i < numCommands; i++) { - validatePayload(this.payload.length >= offset + 1); - const cmdLength = this.payload[offset]; - validatePayload(this.payload.length >= offset + 1 + cmdLength); - this.encapsulated.push( - CommandClass.from(this.host, { - data: this.payload.subarray( - offset + 1, - offset + 1 + cmdLength, - ), - fromEncapsulation: true, - encapCC: this, - origin: options.origin, - frameType: options.frameType, - }), - ); - offset += 1 + cmdLength; - } - } else { - this.encapsulated = options.encapsulated; - for (const cc of options.encapsulated) { - cc.encapsulatingCC = this as any; - } + super(options); + this.encapsulated = options.encapsulated; + for (const cc of options.encapsulated) { + cc.encapsulatingCC = this as any; + // Multi Command CC is inside Multi Channel CC, so the endpoint must be copied + cc.endpointIndex = this.endpointIndex; + } + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultiCommandCCCommandEncapsulation { + validatePayload(raw.payload.length >= 1); + const numCommands = raw.payload[0]; + const encapsulated: CommandClass[] = []; + let offset = 1; + for (let i = 0; i < numCommands; i++) { + validatePayload(raw.payload.length >= offset + 1); + const cmdLength = raw.payload[offset]; + validatePayload(raw.payload.length >= offset + 1 + cmdLength); + encapsulated.push( + CommandClass.parse( + raw.payload.subarray( + offset + 1, + offset + 1 + cmdLength, + ), + ctx, + ), + ); + offset += 1 + cmdLength; } + + return new MultiCommandCCCommandEncapsulation({ + nodeId: ctx.sourceNodeId, + encapsulated, + }); } 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..c83aa03dbff5 100644 --- a/packages/cc/src/cc/MultilevelSensorCC.ts +++ b/packages/cc/src/cc/MultilevelSensorCC.ts @@ -1,4 +1,5 @@ import { + type WithAddress, encodeBitMask, getSensor, getSensorName, @@ -7,12 +8,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,11 +31,16 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetNode, + GetSupportedCCVersion, + GetUserPreferences, + GetValueDB, + LogNode, } from "@zwave-js/host/safe"; -import { num2hex } from "@zwave-js/shared/safe"; +import { type AllOrNone, num2hex } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -40,11 +50,13 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, type CCResponsePredicate, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -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, + endpointIndex: this.endpoint.index, + sensorType: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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,12 +630,12 @@ value: ${mlsResponse.value}${ const sensor = getSensor(propertyKey); if (sensor) return sensor.label; } - return super.translatePropertyKey(applHost, property, propertyKey); + return super.translatePropertyKey(ctx, property, propertyKey); } } // @publicAPI -export interface MultilevelSensorCCReportOptions extends CCCommandOptions { +export interface MultilevelSensorCCReportOptions { type: number; scale: number | Scale; value: number; @@ -622,33 +645,39 @@ export interface MultilevelSensorCCReportOptions extends CCCommandOptions { @useSupervision() export class MultilevelSensorCCReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSensorCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.type = this.payload[0]; - // parseFloatWithScale does its own validation - const { value, scale } = parseFloatWithScale( - this.payload.subarray(1), - ); - this.value = value; - this.scale = scale; - } else { - this.type = options.type; - this.value = options.value; - this.scale = typeof options.scale === "number" - ? options.scale - : options.scale.key; - } + super(options); + + this.type = options.type; + this.value = options.value; + this.scale = typeof options.scale === "number" + ? options.scale + : options.scale.key; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSensorCCReport { + validatePayload(raw.payload.length >= 1); + const type = raw.payload[0]; + + // parseFloatWithScale does its own validation + const { value, scale } = parseFloatWithScale( + raw.payload.subarray(1), + ); + + return new MultilevelSensorCCReport({ + nodeId: ctx.sourceNodeId, + type, + value, + scale, + }); } - 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 +686,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 +708,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { } const supportedScales = this.getValue( - applHost, + ctx, MultilevelSensorCCValues.supportedScales(this.type), ); if (supportedScales?.length) { @@ -704,7 +735,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 +743,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 +752,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) @@ -751,14 +782,11 @@ const testResponseForMultilevelSensorGet: CCResponsePredicate< }; // These options are supported starting in V5 -interface MultilevelSensorCCGetSpecificOptions { +// @publicAPI +export type MultilevelSensorCCGetOptions = AllOrNone<{ sensorType: number; scale: number; -} -// @publicAPI -export type MultilevelSensorCCGetOptions = - | CCCommandOptions - | (CCCommandOptions & MultilevelSensorCCGetSpecificOptions); +}>; @CCCommand(MultilevelSensorCommand.Get) @expectedCCResponse( @@ -767,29 +795,38 @@ export type MultilevelSensorCCGetOptions = ) export class MultilevelSensorCCGet extends MultilevelSensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSensorCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length >= 2) { - this.sensorType = this.payload[0]; - this.scale = (this.payload[1] >> 3) & 0b11; - } + super(options); + if ("sensorType" in options) { + this.sensorType = options.sensorType; + this.scale = options.scale; + } + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSensorCCGet { + if (raw.payload.length >= 2) { + const sensorType = raw.payload[0]; + const scale = (raw.payload[1] >> 3) & 0b11; + return new MultilevelSensorCCGet({ + nodeId: ctx.sourceNodeId, + sensorType, + scale, + }); } else { - if ("sensorType" in options) { - this.sensorType = options.sensorType; - this.scale = options.scale; - } + return new MultilevelSensorCCGet({ + nodeId: ctx.sourceNodeId, + }); } } 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 +836,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,16 +856,14 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface MultilevelSensorCCSupportedSensorReportOptions - extends CCCommandOptions -{ +export interface MultilevelSensorCCSupportedSensorReportOptions { supportedSensorTypes: readonly number[]; } @@ -837,33 +872,38 @@ export class MultilevelSensorCCSupportedSensorReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSensorCCSupportedSensorReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.supportedSensorTypes = parseBitMask(this.payload); - } else { - this.supportedSensorTypes = options.supportedSensorTypes; - } + this.supportedSensorTypes = options.supportedSensorTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSensorCCSupportedSensorReport { + validatePayload(raw.payload.length >= 1); + const supportedSensorTypes = parseBitMask(raw.payload); + + return new MultilevelSensorCCSupportedSensorReport({ + nodeId: ctx.sourceNodeId, + supportedSensorTypes, + }); } // TODO: Use this during interview to precreate values @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)}`) @@ -878,9 +918,7 @@ export class MultilevelSensorCCSupportedSensorReport export class MultilevelSensorCCGetSupportedSensor extends MultilevelSensorCC {} // @publicAPI -export interface MultilevelSensorCCSupportedScaleReportOptions - extends CCCommandOptions -{ +export interface MultilevelSensorCCSupportedScaleReportOptions { sensorType: number; supportedScales: readonly number[]; } @@ -888,24 +926,30 @@ export interface MultilevelSensorCCSupportedScaleReportOptions @CCCommand(MultilevelSensorCommand.SupportedScaleReport) export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSensorCCSupportedScaleReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.sensorType = this.payload[0]; - this.supportedScales = parseBitMask( - Buffer.from([this.payload[1] & 0b1111]), - 0, - ); - } else { - this.sensorType = options.sensorType; - this.supportedScales = options.supportedScales; - } + super(options); + + this.sensorType = options.sensorType; + this.supportedScales = options.supportedScales; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSensorCCSupportedScaleReport { + validatePayload(raw.payload.length >= 2); + const sensorType = raw.payload[0]; + const supportedScales = parseBitMask( + Buffer.from([raw.payload[1] & 0b1111]), + 0, + ); + + return new MultilevelSensorCCSupportedScaleReport({ + nodeId: ctx.sourceNodeId, + sensorType, + supportedScales, + }); } public readonly sensorType: number; @@ -917,17 +961,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 @@ -945,9 +989,7 @@ export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC { } // @publicAPI -export interface MultilevelSensorCCGetSupportedScaleOptions - extends CCCommandOptions -{ +export interface MultilevelSensorCCGetSupportedScaleOptions { sensorType: number; } @@ -955,30 +997,35 @@ export interface MultilevelSensorCCGetSupportedScaleOptions @expectedCCResponse(MultilevelSensorCCSupportedScaleReport) export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSensorCCGetSupportedScaleOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.sensorType = this.payload[0]; - } else { - this.sensorType = options.sensorType; - } + super(options); + this.sensorType = options.sensorType; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSensorCCGetSupportedScale { + validatePayload(raw.payload.length >= 1); + const sensorType = raw.payload[0]; + + return new MultilevelSensorCCGetSupportedScale({ + nodeId: ctx.sourceNodeId, + sensorType, + }); } 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..2c60c409b752 100644 --- a/packages/cc/src/cc/MultilevelSwitchCC.ts +++ b/packages/cc/src/cc/MultilevelSwitchCC.ts @@ -9,14 +9,15 @@ import { NOT_KNOWN, type SupervisionResult, ValueMetadata, + type WithAddress, maybeUnknownToString, parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -32,10 +33,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -228,11 +231,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSwitchCCReport >( cc, @@ -259,13 +262,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, + endpointIndex: this.endpoint.index, targetValue, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -277,13 +280,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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async stopLevelChange(): Promise { @@ -292,12 +295,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, + endpointIndex: this.endpoint.index, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getSupported(): Promise> { @@ -306,11 +309,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< MultilevelSwitchCCSupportedReport >( cc, @@ -468,7 +471,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 +521,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 +563,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,31 +594,31 @@ 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), ); } } // @publicAPI -export interface MultilevelSwitchCCSetOptions extends CCCommandOptions { +export interface MultilevelSwitchCCSetOptions { targetValue: number; // Version >= 2: duration?: Duration | string; @@ -621,36 +628,44 @@ export interface MultilevelSwitchCCSetOptions extends CCCommandOptions { @useSupervision() export class MultilevelSwitchCCSet extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSwitchCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.targetValue = this.payload[0]; + super(options); + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } - if (this.payload.length >= 2) { - this.duration = Duration.parseReport(this.payload[1]); - } - } else { - this.targetValue = options.targetValue; - this.duration = Duration.from(options.duration); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSwitchCCSet { + validatePayload(raw.payload.length >= 1); + const targetValue = raw.payload[0]; + let duration: Duration | undefined; + + if (raw.payload.length >= 2) { + duration = Duration.parseReport(raw.payload[1]); } + + return new MultilevelSwitchCCSet({ + nodeId: ctx.sourceNodeId, + targetValue, + duration, + }); } 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 +673,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,45 +684,55 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface MultilevelSwitchCCReportOptions extends CCCommandOptions { - currentValue: MaybeUnknown; - targetValue: MaybeUnknown; +export interface MultilevelSwitchCCReportOptions { + currentValue?: MaybeUnknown; + targetValue?: MaybeUnknown; duration?: Duration | string; } @CCCommand(MultilevelSwitchCommand.Report) export class MultilevelSwitchCCReport extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | MultilevelSwitchCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.currentValue = - // 0xff is a legacy value for 100% (99) - this.payload[0] === 0xff - ? 99 - : parseMaybeNumber(this.payload[0]); - if (this.version >= 4 && this.payload.length >= 3) { - this.targetValue = parseMaybeNumber(this.payload[1]); - this.duration = Duration.parseReport(this.payload[2]); - } - } else { - this.currentValue = options.currentValue; - this.targetValue = options.targetValue; - this.duration = Duration.from(options.duration); + super(options); + + this.currentValue = options.currentValue; + this.targetValue = options.targetValue; + this.duration = Duration.from(options.duration); + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSwitchCCReport { + validatePayload(raw.payload.length >= 1); + const currentValue: MaybeUnknown | undefined = + // 0xff is a legacy value for 100% (99) + raw.payload[0] === 0xff + ? 99 + : parseMaybeNumber(raw.payload[0]); + let targetValue: MaybeUnknown | undefined; + let duration: Duration | undefined; + + if (raw.payload.length >= 3) { + targetValue = parseMaybeNumber(raw.payload[1]); + duration = Duration.parseReport(raw.payload[2]); } + + return new MultilevelSwitchCCReport({ + nodeId: ctx.sourceNodeId, + currentValue, + targetValue, + duration, + }); } @ccValue(MultilevelSwitchCCValues.targetValue) @@ -719,16 +744,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 +762,7 @@ export class MultilevelSwitchCCReport extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -771,30 +796,37 @@ export type MultilevelSwitchCCStartLevelChangeOptions = @useSupervision() export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & MultilevelSwitchCCStartLevelChangeOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5; - this.ignoreStartLevel = !!ignoreStartLevel; - const direction = (this.payload[0] & 0b0_1_0_00000) >>> 6; - this.direction = direction ? "down" : "up"; - - this.startLevel = this.payload[1]; - - if (this.payload.length >= 3) { - this.duration = Duration.parseSet(this.payload[2]); - } - } else { - this.duration = Duration.from(options.duration); - this.ignoreStartLevel = options.ignoreStartLevel; - this.startLevel = options.startLevel ?? 0; - this.direction = options.direction; + super(options); + this.duration = Duration.from(options.duration); + this.ignoreStartLevel = options.ignoreStartLevel; + this.startLevel = options.startLevel ?? 0; + this.direction = options.direction; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSwitchCCStartLevelChange { + validatePayload(raw.payload.length >= 2); + const ignoreStartLevel = !!((raw.payload[0] & 0b0_0_1_00000) >>> 5); + const direction = ((raw.payload[0] & 0b0_1_0_00000) >>> 6) + ? "down" + : "up"; + const startLevel = raw.payload[1]; + let duration: Duration | undefined; + if (raw.payload.length >= 3) { + duration = Duration.parseSet(raw.payload[2]); } + + return new MultilevelSwitchCCStartLevelChange({ + nodeId: ctx.sourceNodeId, + ignoreStartLevel, + direction, + startLevel, + duration, + }); } public duration: Duration | undefined; @@ -802,7 +834,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 +843,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 +853,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 +867,7 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -852,40 +885,44 @@ export interface MultilevelSwitchCCSupportedReportOptions { @CCCommand(MultilevelSwitchCommand.SupportedReport) export class MultilevelSwitchCCSupportedReport extends MultilevelSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & MultilevelSwitchCCSupportedReportOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.switchType = this.payload[0] & 0b11111; - // We do not support the deprecated secondary switch type - } else { - this.switchType = options.switchType; - } + this.switchType = options.switchType; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): MultilevelSwitchCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const switchType: SwitchType = raw.payload[0] & 0b11111; + + return new MultilevelSwitchCCSupportedReport({ + nodeId: ctx.sourceNodeId, + switchType, + }); } // This is the primary switch type. We're not supporting secondary switch types @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..993252cb840a 100644 --- a/packages/cc/src/cc/NoOperationCC.ts +++ b/packages/cc/src/cc/NoOperationCC.ts @@ -1,5 +1,4 @@ import { CommandClasses, MessagePriority } from "@zwave-js/core/safe"; -import type { Message } from "@zwave-js/serial"; import { PhysicalCCAPI } from "../lib/API"; import { CommandClass } from "../lib/CommandClass"; import { @@ -7,7 +6,6 @@ import { commandClass, implementedVersion, } from "../lib/CommandClassDecorators"; -import { isCommandClassContainer } from "../lib/ICommandClassContainer"; // @noSetValueAPI This CC has no set-type commands // @noInterview There's nothing to interview here @@ -15,10 +13,10 @@ 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, + endpointIndex: this.endpoint.index, }), { ...this.commandOptions, @@ -37,13 +35,3 @@ export class NoOperationCCAPI extends PhysicalCCAPI { export class NoOperationCC extends CommandClass { declare ccCommand: undefined; } - -/** - * @publicAPI - * Tests if a given message is a ping - */ -export function messageIsPing( - msg: T, -): msg is T & { command: NoOperationCC } { - return isCommandClassContainer(msg) && msg.command instanceof NoOperationCC; -} diff --git a/packages/cc/src/cc/NodeNamingCC.ts b/packages/cc/src/cc/NodeNamingCC.ts index 53ebeb26d04a..0ee09bc68ee4 100644 --- a/packages/cc/src/cc/NodeNamingCC.ts +++ b/packages/cc/src/cc/NodeNamingCC.ts @@ -5,14 +5,15 @@ import { MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, 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 { @@ -26,10 +27,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -133,11 +134,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NodeNamingAndLocationCCNameReport >( cc, @@ -153,12 +154,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, + endpointIndex: this.endpoint.index, name, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getLocation(): Promise> { @@ -167,11 +168,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NodeNamingAndLocationCCLocationReport >( cc, @@ -189,12 +190,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, + endpointIndex: this.endpoint.index, location, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -209,51 +210,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", }); @@ -262,9 +267,7 @@ export class NodeNamingAndLocationCC extends CommandClass { } // @publicAPI -export interface NodeNamingAndLocationCCNameSetOptions - extends CCCommandOptions -{ +export interface NodeNamingAndLocationCCNameSetOptions { name: string; } @@ -272,26 +275,30 @@ export interface NodeNamingAndLocationCCNameSetOptions @useSupervision() export class NodeNamingAndLocationCCNameSet extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | NodeNamingAndLocationCCNameSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.name = options.name; - } + super(options); + this.name = options.name; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): NodeNamingAndLocationCCNameSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new NodeNamingAndLocationCCNameSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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,40 +316,56 @@ 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 }, }; } } +// @publicAPI +export interface NodeNamingAndLocationCCNameReportOptions { + name: string; +} + @CCCommand(NodeNamingAndLocationCommand.NameReport) export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, + options: WithAddress, ) { - super(host, options); - const encoding = this.payload[0] === 2 ? "utf16le" : "ascii"; - let nameBuffer = this.payload.subarray(1); + super(options); + this.name = options.name; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NodeNamingAndLocationCCNameReport { + validatePayload(raw.payload.length >= 1); + const encoding = raw.payload[0] === 2 ? "utf16le" : "ascii"; + let nameBuffer = raw.payload.subarray(1); if (encoding === "utf16le") { validatePayload(nameBuffer.length % 2 === 0); // Z-Wave expects UTF16 BE nameBuffer = nameBuffer.swap16(); } - this.name = nameBuffer.toString(encoding); + + return new NodeNamingAndLocationCCNameReport({ + nodeId: ctx.sourceNodeId, + name: nameBuffer.toString(encoding), + }); } @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 }, }; } @@ -353,9 +376,7 @@ export class NodeNamingAndLocationCCNameReport extends NodeNamingAndLocationCC { export class NodeNamingAndLocationCCNameGet extends NodeNamingAndLocationCC {} // @publicAPI -export interface NodeNamingAndLocationCCLocationSetOptions - extends CCCommandOptions -{ +export interface NodeNamingAndLocationCCLocationSetOptions { location: string; } @@ -365,26 +386,30 @@ export class NodeNamingAndLocationCCLocationSet extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | NodeNamingAndLocationCCLocationSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.location = options.location; - } + super(options); + this.location = options.location; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): NodeNamingAndLocationCCLocationSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new NodeNamingAndLocationCCLocationSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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,42 +427,58 @@ 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 }, }; } } +// @publicAPI +export interface NodeNamingAndLocationCCLocationReportOptions { + location: string; +} + @CCCommand(NodeNamingAndLocationCommand.LocationReport) export class NodeNamingAndLocationCCLocationReport extends NodeNamingAndLocationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, + options: WithAddress, ) { - super(host, options); - const encoding = this.payload[0] === 2 ? "utf16le" : "ascii"; - let locationBuffer = this.payload.subarray(1); + super(options); + this.location = options.location; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NodeNamingAndLocationCCLocationReport { + validatePayload(raw.payload.length >= 1); + const encoding = raw.payload[0] === 2 ? "utf16le" : "ascii"; + let locationBuffer = raw.payload.subarray(1); if (encoding === "utf16le") { validatePayload(locationBuffer.length % 2 === 0); // Z-Wave expects UTF16 BE locationBuffer = locationBuffer.swap16(); } - this.location = locationBuffer.toString(encoding); + + return new NodeNamingAndLocationCCLocationReport({ + nodeId: ctx.sourceNodeId, + location: locationBuffer.toString(encoding), + }); } @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..7c761041fcaf 100644 --- a/packages/cc/src/cc/NotificationCC.ts +++ b/packages/cc/src/cc/NotificationCC.ts @@ -2,6 +2,7 @@ import { type Notification, type NotificationState, type NotificationValue, + type WithAddress, getNotification, getNotificationEventName, getNotificationName, @@ -11,15 +12,19 @@ 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, + SecurityClass, type SinglecastCC, type SupervisionResult, + type SupportsCC, type ValueID, ValueMetadata, type ValueMetadataNumeric, @@ -32,9 +37,13 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; @@ -47,11 +56,13 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + CCRaw, CommandClass, - type CommandClassDeserializationOptions, + type InterviewContext, InvalidCC, - gotDeserializationOptions, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -213,10 +224,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( @@ -283,19 +294,19 @@ export class NotificationCCAPI extends PhysicalCCAPI { * @internal */ public async getInternal( - options: NotificationCCGetSpecificOptions, + options: NotificationCCGetOptions, ): Promise { this.assertSupportsCommand( NotificationCommand, NotificationCommand.Get, ); - const cc = new NotificationCCGet(this.applHost, { + const cc = new NotificationCCGet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand( + return this.host.sendCommand( cc, this.commandOptions, ); @@ -310,24 +321,23 @@ 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, + endpointIndex: this.endpoint.index, ...options, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - public async get(options: NotificationCCGetSpecificOptions) { + public async get(options: NotificationCCGetOptions) { const response = await this.getInternal(options); if (response) { return pick(response, [ "notificationStatus", "notificationEvent", "alarmLevel", - "zensorNetSourceNodeId", "eventParameters", "sequenceNumber", ]); @@ -344,13 +354,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, + endpointIndex: 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 +370,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NotificationCCSupportedReport >( cc, @@ -387,12 +397,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, + endpointIndex: this.endpoint.index, notificationType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< NotificationCCEventSupportedReport >( cc, @@ -490,11 +500,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 +514,7 @@ export class NotificationCC extends CommandClass { try { const groupsIssueingNotifications = AssociationGroupInfoCC .findGroupsForIssuedCommand( - applHost, + ctx, node, this.ccId, NotificationCommand.Report, @@ -515,7 +525,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 +561,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 +592,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 +610,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 +619,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 +643,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 +664,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 +680,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 +705,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 +737,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 +755,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 +795,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 +807,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 +820,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 +834,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 +854,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 +866,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 +884,7 @@ export class NotificationCC extends CommandClass { // Remember when we did this this.setValue( - applHost, + ctx, NotificationCCValues.lastRefresh, Date.now(), ); @@ -879,18 +893,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, ); @@ -902,7 +922,7 @@ export class NotificationCC extends CommandClass { } // @publicAPI -export interface NotificationCCSetOptions extends CCCommandOptions { +export interface NotificationCCSetOptions { notificationType: number; notificationStatus: boolean; } @@ -911,33 +931,39 @@ export interface NotificationCCSetOptions extends CCCommandOptions { @useSupervision() export class NotificationCCSet extends NotificationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | NotificationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.notificationType = this.payload[0]; - this.notificationStatus = this.payload[1] === 0xff; - } else { - this.notificationType = options.notificationType; - this.notificationStatus = options.notificationStatus; - } + super(options); + this.notificationType = options.notificationType; + this.notificationStatus = options.notificationStatus; } + + public static from(raw: CCRaw, ctx: CCParsingContext): NotificationCCSet { + validatePayload(raw.payload.length >= 2); + const notificationType = raw.payload[0]; + const notificationStatus = raw.payload[1] === 0xff; + + return new NotificationCCSet({ + nodeId: ctx.sourceNodeId, + notificationType, + notificationStatus, + }); + } + 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, @@ -947,84 +973,97 @@ export class NotificationCCSet extends NotificationCC { } // @publicAPI -export type NotificationCCReportOptions = - | { - alarmType: number; - alarmLevel: number; - } - | { - notificationType: number; - notificationEvent: number; - eventParameters?: Buffer; - sequenceNumber?: number; - }; +export type NotificationCCReportOptions = { + alarmType?: number; + alarmLevel?: number; + notificationType?: number; + notificationEvent?: number; + notificationStatus?: number; + eventParameters?: Buffer; + sequenceNumber?: number; +}; @CCCommand(NotificationCommand.Report) @useSupervision() export class NotificationCCReport extends NotificationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (NotificationCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - 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]; - } - // 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 - if (this.payload.length >= 7) { - this.notificationStatus = this.payload[3]; - this.notificationType = this.payload[4]; - this.notificationEvent = this.payload[5]; - - const containsSeqNum = !!(this.payload[6] & 0b1000_0000); - const numEventParams = this.payload[6] & 0b11111; - if (numEventParams > 0) { - validatePayload(this.payload.length >= 7 + numEventParams); - this.eventParameters = Buffer.from( - this.payload.subarray(7, 7 + numEventParams), - ); - } - if (containsSeqNum) { - validatePayload( - this.payload.length >= 7 + numEventParams + 1, - ); - this.sequenceNumber = this.payload[7 + numEventParams]; - } - } + if (options.alarmType != undefined) { + this.alarmType = options.alarmType; + this.alarmLevel = options.alarmLevel; + } - // Store the V1 alarm values if they exist - } else { - // Create a notification to send - 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; - this.notificationEvent = options.notificationEvent; - this.eventParameters = options.eventParameters; - this.sequenceNumber = options.sequenceNumber; - } + if (options.notificationType != undefined) { + this.notificationType = options.notificationType; + this.notificationStatus = options.notificationStatus ?? true; + this.notificationEvent = options.notificationEvent; + this.eventParameters = options.eventParameters; + this.sequenceNumber = options.sequenceNumber; } } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NotificationCCReport { + validatePayload(raw.payload.length >= 2); + const alarmType = raw.payload[0]; + const alarmLevel = raw.payload[1]; + + // Byte 2 used to be zensorNetSourceNodeId in V2 and V3, but we don't care about that + + if (raw.payload.length < 7) { + return new NotificationCCReport({ + nodeId: ctx.sourceNodeId, + alarmType, + alarmLevel, + }); + } + + // 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 + + const notificationStatus = raw.payload[3]; + const notificationType = raw.payload[4]; + const notificationEvent = raw.payload[5]; + + const containsSeqNum = !!(raw.payload[6] & 0b1000_0000); + const numEventParams = raw.payload[6] & 0b11111; + let eventParameters: Buffer | undefined; + if (numEventParams > 0) { + validatePayload(raw.payload.length >= 7 + numEventParams); + eventParameters = Buffer.from( + raw.payload.subarray(7, 7 + numEventParams), + ); + } + let sequenceNumber: number | undefined; + if (containsSeqNum) { + validatePayload( + raw.payload.length >= 7 + numEventParams + 1, + ); + sequenceNumber = raw.payload[7 + numEventParams]; + } + + return new NotificationCCReport({ + nodeId: ctx.sourceNodeId, + alarmType, + alarmLevel, + notificationStatus, + notificationType, + notificationEvent, + eventParameters, + sequenceNumber, + }); + } + + 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 +1071,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 +1084,7 @@ export class NotificationCCReport extends NotificationCC { const supportedNotificationEvents = this.getValue< readonly number[] >( - applHost, + ctx, NotificationCCValues.supportedNotificationEvents( this.alarmType, ), @@ -1055,7 +1094,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 +1106,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 +1116,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 +1143,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 +1166,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 +1175,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 +1219,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 +1262,12 @@ export class NotificationCCReport extends NotificationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } - private parseEventParameters(applHost: ZWaveApplicationHost): void { + private parseEventParameters(ctx: PersistValuesContext): void { // This only makes sense for V2+ notifications if ( this.notificationType == undefined @@ -1281,12 +1316,21 @@ 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, { - data: this.eventParameters, - fromEncapsulation: true, - encapCC: this, + const cc = CommandClass.parse(this.eventParameters, { + ...ctx, + frameType: "singlecast", + sourceNodeId: this.nodeId as number, + // Security encapsulation is handled outside of this CC, + // so it is not needed here: + hasSecurityClass: () => false, + getHighestSecurityClass: () => SecurityClass.None, + setSecurityClass: () => {}, + securityManager: undefined, + securityManager2: undefined, + securityManagerLR: undefined, }); validatePayload(!(cc instanceof InvalidCC)); + cc.encapsulatingCC = this as any; if (isNotificationEventPayload(cc)) { this.eventParameters = cc @@ -1310,10 +1354,7 @@ export class NotificationCCReport extends NotificationCC { === ZWaveErrorCodes.PacketFormat_InvalidPayload && Buffer.isBuffer(this.eventParameters) ) { - const ccId = CommandClass.getCommandClass( - this.eventParameters, - ); - const ccCommand = CommandClass.getCCCommand( + const { ccId, ccCommand } = CCRaw.parse( this.eventParameters, ); if ( @@ -1327,7 +1368,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 +1412,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 +1430,7 @@ export class NotificationCCReport extends NotificationCC { ZWaveErrorCodes.Argument_Invalid, ); } + const controlByte = (this.sequenceNumber != undefined ? 0b1000_0000 : 0) | ((this.eventParameters?.length ?? 0) & 0b11111); @@ -1422,12 +1455,19 @@ 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); } } -type NotificationCCGetSpecificOptions = +// @publicAPI +export type NotificationCCGetOptions = | { alarmType: number; } @@ -1435,35 +1475,42 @@ type NotificationCCGetSpecificOptions = notificationType: number; notificationEvent?: number; }; -// @publicAPI -export type NotificationCCGetOptions = - & CCCommandOptions - & NotificationCCGetSpecificOptions; @CCCommand(NotificationCommand.Get) @expectedCCResponse(NotificationCCReport) export class NotificationCCGet extends NotificationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | NotificationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.alarmType = this.payload[0] || undefined; - if (this.payload.length >= 2) { - this.notificationType = this.payload[1] || undefined; - if (this.payload.length >= 3 && this.notificationType != 0xff) { - this.notificationEvent = this.payload[2]; - } - } + super(options); + if ("alarmType" in options) { + this.alarmType = options.alarmType; } else { - if ("alarmType" in options) { - this.alarmType = options.alarmType; - } else { - this.notificationType = options.notificationType; - this.notificationEvent = options.notificationEvent; + this.notificationType = options.notificationType; + this.notificationEvent = options.notificationEvent; + } + } + + public static from(raw: CCRaw, ctx: CCParsingContext): NotificationCCGet { + validatePayload(raw.payload.length >= 1); + + if (raw.payload.length >= 2) { + const notificationType = raw.payload[1]; + let notificationEvent: number | undefined; + if (raw.payload.length >= 3 && notificationType != 0xff) { + notificationEvent = raw.payload[2]; } + return new NotificationCCGet({ + nodeId: ctx.sourceNodeId, + notificationType, + notificationEvent, + }); + } else { + const alarmType = raw.payload[0]; + return new NotificationCCGet({ + nodeId: ctx.sourceNodeId, + alarmType, + }); } } @@ -1473,23 +1520,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,14 +1549,14 @@ export class NotificationCCGet extends NotificationCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface NotificationCCSupportedReportOptions extends CCCommandOptions { +export interface NotificationCCSupportedReportOptions { supportsV1Alarm: boolean; supportedNotificationTypes: number[]; } @@ -1521,35 +1564,40 @@ export interface NotificationCCSupportedReportOptions extends CCCommandOptions { @CCCommand(NotificationCommand.SupportedReport) export class NotificationCCSupportedReport extends NotificationCC { public constructor( - host: ZWaveHost, - options: - | NotificationCCSupportedReportOptions - | CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.supportsV1Alarm = !!(this.payload[0] & 0b1000_0000); - const numBitMaskBytes = this.payload[0] & 0b0001_1111; - validatePayload( - numBitMaskBytes > 0, - this.payload.length >= 1 + numBitMaskBytes, - ); - const notificationBitMask = this.payload.subarray( - 1, - 1 + numBitMaskBytes, - ); - this.supportedNotificationTypes = parseBitMask( - notificationBitMask, - // bit 0 is ignored, but counting still starts at 1, so the first bit must have the value 0 - 0, - ); - } else { - this.supportsV1Alarm = options.supportsV1Alarm; - this.supportedNotificationTypes = - options.supportedNotificationTypes; - } + this.supportsV1Alarm = options.supportsV1Alarm; + this.supportedNotificationTypes = options.supportedNotificationTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NotificationCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const supportsV1Alarm = !!(raw.payload[0] & 0b1000_0000); + const numBitMaskBytes = raw.payload[0] & 0b0001_1111; + validatePayload( + numBitMaskBytes > 0, + raw.payload.length >= 1 + numBitMaskBytes, + ); + const notificationBitMask = raw.payload.subarray( + 1, + 1 + numBitMaskBytes, + ); + const supportedNotificationTypes = parseBitMask( + notificationBitMask, + // bit 0 is ignored, but counting still starts at 1, so the first bit must have the value 0 + 0, + ); + + return new NotificationCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportsV1Alarm, + supportedNotificationTypes, + }); } @ccValue(NotificationCCValues.supportsV1Alarm) @@ -1558,7 +1606,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 +1618,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 @@ -1593,9 +1641,7 @@ export class NotificationCCSupportedReport extends NotificationCC { export class NotificationCCSupportedGet extends NotificationCC {} // @publicAPI -export interface NotificationCCEventSupportedReportOptions - extends CCCommandOptions -{ +export interface NotificationCCEventSupportedReportOptions { notificationType: number; supportedEvents: number[]; } @@ -1603,42 +1649,52 @@ export interface NotificationCCEventSupportedReportOptions @CCCommand(NotificationCommand.EventSupportedReport) export class NotificationCCEventSupportedReport extends NotificationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | NotificationCCEventSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.notificationType = this.payload[0]; - const numBitMaskBytes = this.payload[1] & 0b000_11111; - if (numBitMaskBytes === 0) { - // Notification type is not supported - this.supportedEvents = []; - return; - } + super(options); - validatePayload(this.payload.length >= 2 + numBitMaskBytes); - const eventBitMask = this.payload.subarray(2, 2 + numBitMaskBytes); - this.supportedEvents = parseBitMask( - eventBitMask, - // In this mask, bit 0 is ignored, but counting still starts at 1, so the first bit must have the value 0 - 0, - ); - } else { - this.notificationType = options.notificationType; - this.supportedEvents = options.supportedEvents; + this.notificationType = options.notificationType; + this.supportedEvents = options.supportedEvents; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NotificationCCEventSupportedReport { + validatePayload(raw.payload.length >= 1); + const notificationType = raw.payload[0]; + const numBitMaskBytes = raw.payload[1] & 0b000_11111; + + if (numBitMaskBytes === 0) { + // Notification type is not supported + return new NotificationCCEventSupportedReport({ + nodeId: ctx.sourceNodeId, + notificationType, + supportedEvents: [], + }); } + + validatePayload(raw.payload.length >= 2 + numBitMaskBytes); + const eventBitMask = raw.payload.subarray(2, 2 + numBitMaskBytes); + const supportedEvents = parseBitMask( + eventBitMask, + // In this mask, bit 0 is ignored, but counting still starts at 1, so the first bit must have the value 0 + 0, + ); + + return new NotificationCCEventSupportedReport({ + nodeId: ctx.sourceNodeId, + notificationType, + supportedEvents, + }); } - 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 +1707,7 @@ export class NotificationCCEventSupportedReport extends NotificationCC { if (!notification) { // This is an unknown notification this.setMetadata( - applHost, + ctx, NotificationCCValues.unknownNotificationType( this.notificationType, ), @@ -1673,12 +1729,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 +1747,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 +1759,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 @@ -1728,9 +1784,7 @@ export class NotificationCCEventSupportedReport extends NotificationCC { } // @publicAPI -export interface NotificationCCEventSupportedGetOptions - extends CCCommandOptions -{ +export interface NotificationCCEventSupportedGetOptions { notificationType: number; } @@ -1738,30 +1792,35 @@ export interface NotificationCCEventSupportedGetOptions @expectedCCResponse(NotificationCCEventSupportedReport) export class NotificationCCEventSupportedGet extends NotificationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | NotificationCCEventSupportedGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.notificationType = this.payload[0]; - } else { - this.notificationType = options.notificationType; - } + super(options); + this.notificationType = options.notificationType; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): NotificationCCEventSupportedGet { + validatePayload(raw.payload.length >= 1); + const notificationType = raw.payload[0]; + + return new NotificationCCEventSupportedGet({ + nodeId: ctx.sourceNodeId, + notificationType, + }); } 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..ce06831e3180 100644 --- a/packages/cc/src/cc/PowerlevelCC.ts +++ b/packages/cc/src/cc/PowerlevelCC.ts @@ -5,20 +5,20 @@ import { type MessageRecord, NodeStatus, type SupervisionResult, + type WithAddress, ZWaveError, ZWaveErrorCodes, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { + CCEncodingContext, + CCParsingContext, + 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"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -208,55 +208,63 @@ export class PowerlevelCC extends CommandClass { // @publicAPI export type PowerlevelCCSetOptions = - & CCCommandOptions - & ( - | { - powerlevel: Powerlevel; - timeout: number; - } - | { - powerlevel: (typeof Powerlevel)["Normal Power"]; - timeout?: undefined; - } - ); + | { + powerlevel: Powerlevel; + timeout: number; + } + | { + powerlevel: (typeof Powerlevel)["Normal Power"]; + timeout?: undefined; + }; @CCCommand(PowerlevelCommand.Set) @useSupervision() export class PowerlevelCCSet extends PowerlevelCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | PowerlevelCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.powerlevel = this.payload[0]; - if (this.powerlevel !== Powerlevel["Normal Power"]) { - this.timeout = this.payload[1]; + super(options); + this.powerlevel = options.powerlevel; + if (options.powerlevel !== Powerlevel["Normal Power"]) { + if (options.timeout < 1 || options.timeout > 255) { + throw new ZWaveError( + `The timeout parameter must be between 1 and 255.`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.timeout = options.timeout; + } + } + + public static from(raw: CCRaw, ctx: CCParsingContext): PowerlevelCCSet { + validatePayload(raw.payload.length >= 1); + const powerlevel: Powerlevel = raw.payload[0]; + + if (powerlevel === Powerlevel["Normal Power"]) { + return new PowerlevelCCSet({ + nodeId: ctx.sourceNodeId, + powerlevel, + }); } else { - this.powerlevel = options.powerlevel; - if (options.powerlevel !== Powerlevel["Normal Power"]) { - if (options.timeout < 1 || options.timeout > 255) { - throw new ZWaveError( - `The timeout parameter must be between 1 and 255.`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.timeout = options.timeout; - } + validatePayload(raw.payload.length >= 2); + const timeout = raw.payload[1]; + return new PowerlevelCCSet({ + nodeId: ctx.sourceNodeId, + powerlevel, + timeout, + }); } } 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 +272,7 @@ export class PowerlevelCCSet extends PowerlevelCC { message.timeout = `${this.timeout} s`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -282,33 +290,43 @@ export type PowerlevelCCReportOptions = { @CCCommand(PowerlevelCommand.Report) export class PowerlevelCCReport extends PowerlevelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (PowerlevelCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - this.powerlevel = this.payload[0]; - if (this.powerlevel !== Powerlevel["Normal Power"]) { - this.timeout = this.payload[1]; - } + this.powerlevel = options.powerlevel; + this.timeout = options.timeout; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): PowerlevelCCReport { + validatePayload(raw.payload.length >= 1); + const powerlevel: Powerlevel = raw.payload[0]; + + if (powerlevel === Powerlevel["Normal Power"]) { + return new PowerlevelCCReport({ + nodeId: ctx.sourceNodeId, + powerlevel, + }); } else { - this.powerlevel = options.powerlevel; - this.timeout = options.timeout; + validatePayload(raw.payload.length >= 2); + const timeout = raw.payload[1]; + return new PowerlevelCCReport({ + nodeId: ctx.sourceNodeId, + powerlevel, + timeout, + }); } } 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 +334,7 @@ export class PowerlevelCCReport extends PowerlevelCC { message.timeout = `${this.timeout} s`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -327,7 +345,7 @@ export class PowerlevelCCReport extends PowerlevelCC { export class PowerlevelCCGet extends PowerlevelCC {} // @publicAPI -export interface PowerlevelCCTestNodeSetOptions extends CCCommandOptions { +export interface PowerlevelCCTestNodeSetOptions { testNodeId: number; powerlevel: Powerlevel; testFrameCount: number; @@ -337,37 +355,44 @@ export interface PowerlevelCCTestNodeSetOptions extends CCCommandOptions { @useSupervision() export class PowerlevelCCTestNodeSet extends PowerlevelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | PowerlevelCCTestNodeSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.testNodeId = this.payload[0]; - this.powerlevel = this.payload[1]; - this.testFrameCount = this.payload.readUInt16BE(2); - } else { - this.testNodeId = options.testNodeId; - this.powerlevel = options.powerlevel; - this.testFrameCount = options.testFrameCount; - } + super(options); + this.testNodeId = options.testNodeId; + this.powerlevel = options.powerlevel; + this.testFrameCount = options.testFrameCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): PowerlevelCCTestNodeSet { + validatePayload(raw.payload.length >= 4); + const testNodeId = raw.payload[0]; + const powerlevel: Powerlevel = raw.payload[1]; + const testFrameCount = raw.payload.readUInt16BE(2); + + return new PowerlevelCCTestNodeSet({ + nodeId: ctx.sourceNodeId, + testNodeId, + powerlevel, + testFrameCount, + }); } public testNodeId: number; 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,30 +412,37 @@ export interface PowerlevelCCTestNodeReportOptions { @CCCommand(PowerlevelCommand.TestNodeReport) export class PowerlevelCCTestNodeReport extends PowerlevelCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (PowerlevelCCTestNodeReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.testNodeId = this.payload[0]; - this.status = this.payload[1]; - this.acknowledgedFrames = this.payload.readUInt16BE(2); - } else { - this.testNodeId = options.testNodeId; - this.status = options.status; - this.acknowledgedFrames = options.acknowledgedFrames; - } + this.testNodeId = options.testNodeId; + this.status = options.status; + this.acknowledgedFrames = options.acknowledgedFrames; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): PowerlevelCCTestNodeReport { + validatePayload(raw.payload.length >= 4); + const testNodeId = raw.payload[0]; + const status: PowerlevelTestStatus = raw.payload[1]; + const acknowledgedFrames = raw.payload.readUInt16BE(2); + + return new PowerlevelCCTestNodeReport({ + nodeId: ctx.sourceNodeId, + testNodeId, + status, + acknowledgedFrames, + }); } public testNodeId: number; public status: PowerlevelTestStatus; public acknowledgedFrames: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.testNodeId, this.status, @@ -419,12 +451,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..d935417e5425 100644 --- a/packages/cc/src/cc/ProtectionCC.ts +++ b/packages/cc/src/cc/ProtectionCC.ts @@ -8,6 +8,7 @@ import { type SupervisionResult, Timeout, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -15,9 +16,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -32,10 +33,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -220,11 +223,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -240,13 +243,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, + endpointIndex: 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 +259,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCSupportedReport >( cc, @@ -282,11 +285,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCExclusiveControlReport >( cc, @@ -304,12 +307,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, + endpointIndex: 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 +321,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ProtectionCCTimeoutReport >( cc, @@ -340,12 +343,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, + endpointIndex: this.endpoint.index, timeout, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -355,18 +358,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 +382,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 +408,7 @@ RF protection states: ${ .map((str) => `\n· ${str}`) .join("") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { message: logMessage, direction: "inbound", }); @@ -412,34 +417,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 +458,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 +466,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 +481,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`, @@ -492,7 +499,7 @@ rf ${getEnumMemberName(RFProtectionState, protectionResp.rf)}`; } // @publicAPI -export interface ProtectionCCSetOptions extends CCCommandOptions { +export interface ProtectionCCSetOptions { local: LocalProtectionState; rf?: RFProtectionState; } @@ -501,33 +508,37 @@ export interface ProtectionCCSetOptions extends CCCommandOptions { @useSupervision() export class ProtectionCCSet extends ProtectionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | ProtectionCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.local = options.local; - this.rf = options.rf; - } + super(options); + this.local = options.local; + this.rf = options.rf; + } + + public static from(_raw: CCRaw, _ctx: CCParsingContext): ProtectionCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ProtectionCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 +546,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,24 +557,43 @@ export class ProtectionCCSet extends ProtectionCC { message.rf = getEnumMemberName(RFProtectionState, this.rf); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface ProtectionCCReportOptions { + local: LocalProtectionState; + rf?: RFProtectionState; +} + @CCCommand(ProtectionCommand.Report) export class ProtectionCCReport extends ProtectionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.local = this.payload[0] & 0b1111; - if (this.payload.length >= 2) { - this.rf = this.payload[1] & 0b1111; + super(options); + + // TODO: Check implementation: + this.local = options.local; + this.rf = options.rf; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ProtectionCCReport { + validatePayload(raw.payload.length >= 1); + const local: LocalProtectionState = raw.payload[0] & 0b1111; + let rf: RFProtectionState | undefined; + if (raw.payload.length >= 2) { + rf = raw.payload[1] & 0b1111; } + + return new ProtectionCCReport({ + nodeId: ctx.sourceNodeId, + local, + rf, + }); } @ccValue(ProtectionCCValues.localProtectionState) @@ -572,7 +602,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 +610,7 @@ export class ProtectionCCReport extends ProtectionCC { message.rf = getEnumMemberName(RFProtectionState, this.rf); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -590,32 +620,59 @@ export class ProtectionCCReport extends ProtectionCC { @expectedCCResponse(ProtectionCCReport) export class ProtectionCCGet extends ProtectionCC {} +// @publicAPI +export interface ProtectionCCSupportedReportOptions { + supportsTimeout: boolean; + supportsExclusiveControl: boolean; + supportedLocalStates: LocalProtectionState[]; + supportedRFStates: RFProtectionState[]; +} + @CCCommand(ProtectionCommand.SupportedReport) export class ProtectionCCSupportedReport extends ProtectionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 5); - this.supportsTimeout = !!(this.payload[0] & 0b1); - this.supportsExclusiveControl = !!(this.payload[0] & 0b10); - this.supportedLocalStates = parseBitMask( - this.payload.subarray(1, 3), + super(options); + + // TODO: Check implementation: + this.supportsTimeout = options.supportsTimeout; + this.supportsExclusiveControl = options.supportsExclusiveControl; + this.supportedLocalStates = options.supportedLocalStates; + this.supportedRFStates = options.supportedRFStates; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ProtectionCCSupportedReport { + validatePayload(raw.payload.length >= 5); + const supportsTimeout = !!(raw.payload[0] & 0b1); + const supportsExclusiveControl = !!(raw.payload[0] & 0b10); + const supportedLocalStates: LocalProtectionState[] = parseBitMask( + raw.payload.subarray(1, 3), LocalProtectionState.Unprotected, ); - this.supportedRFStates = parseBitMask( - this.payload.subarray(3, 5), + const supportedRFStates: RFProtectionState[] = parseBitMask( + raw.payload.subarray(3, 5), RFProtectionState.Unprotected, ); + + return new ProtectionCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportsTimeout, + supportsExclusiveControl, + supportedLocalStates, + supportedRFStates, + }); } - 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 +681,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 +704,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, @@ -672,23 +729,41 @@ export class ProtectionCCSupportedReport extends ProtectionCC { @expectedCCResponse(ProtectionCCSupportedReport) export class ProtectionCCSupportedGet extends ProtectionCC {} +// @publicAPI +export interface ProtectionCCExclusiveControlReportOptions { + exclusiveControlNodeId: number; +} + @CCCommand(ProtectionCommand.ExclusiveControlReport) export class ProtectionCCExclusiveControlReport extends ProtectionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.exclusiveControlNodeId = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.exclusiveControlNodeId = options.exclusiveControlNodeId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ProtectionCCExclusiveControlReport { + validatePayload(raw.payload.length >= 1); + const exclusiveControlNodeId = raw.payload[0]; + + return new ProtectionCCExclusiveControlReport({ + nodeId: ctx.sourceNodeId, + exclusiveControlNodeId, + }); } @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, }, @@ -701,9 +776,7 @@ export class ProtectionCCExclusiveControlReport extends ProtectionCC { export class ProtectionCCExclusiveControlGet extends ProtectionCC {} // @publicAPI -export interface ProtectionCCExclusiveControlSetOptions - extends CCCommandOptions -{ +export interface ProtectionCCExclusiveControlSetOptions { exclusiveControlNodeId: number; } @@ -712,33 +785,37 @@ export interface ProtectionCCExclusiveControlSetOptions @useSupervision() export class ProtectionCCExclusiveControlSet extends ProtectionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ProtectionCCExclusiveControlSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.exclusiveControlNodeId = options.exclusiveControlNodeId; - } + super(options); + this.exclusiveControlNodeId = options.exclusiveControlNodeId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ProtectionCCExclusiveControlSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ProtectionCCExclusiveControlSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, }, @@ -746,23 +823,41 @@ export class ProtectionCCExclusiveControlSet extends ProtectionCC { } } +// @publicAPI +export interface ProtectionCCTimeoutReportOptions { + timeout: Timeout; +} + @CCCommand(ProtectionCommand.TimeoutReport) export class ProtectionCCTimeoutReport extends ProtectionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - this.timeout = Timeout.parse(this.payload[0]); + super(options); + + // TODO: Check implementation: + this.timeout = options.timeout; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ProtectionCCTimeoutReport { + validatePayload(raw.payload.length >= 1); + const timeout: Timeout = Timeout.parse(raw.payload[0]); + + return new ProtectionCCTimeoutReport({ + nodeId: ctx.sourceNodeId, + timeout, + }); } @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() }, }; } @@ -773,7 +868,7 @@ export class ProtectionCCTimeoutReport extends ProtectionCC { export class ProtectionCCTimeoutGet extends ProtectionCC {} // @publicAPI -export interface ProtectionCCTimeoutSetOptions extends CCCommandOptions { +export interface ProtectionCCTimeoutSetOptions { timeout: Timeout; } @@ -782,33 +877,37 @@ export interface ProtectionCCTimeoutSetOptions extends CCCommandOptions { @useSupervision() export class ProtectionCCTimeoutSet extends ProtectionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ProtectionCCTimeoutSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.timeout = options.timeout; - } + super(options); + this.timeout = options.timeout; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ProtectionCCTimeoutSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ProtectionCCTimeoutSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..1800d3df1bf7 100644 --- a/packages/cc/src/cc/SceneActivationCC.ts +++ b/packages/cc/src/cc/SceneActivationCC.ts @@ -2,6 +2,7 @@ import type { MessageOrCCLogEntry, MessageRecord, SupervisionResult, + WithAddress, } from "@zwave-js/core/safe"; import { CommandClasses, @@ -10,7 +11,11 @@ import { ValueMetadata, validatePayload, } from "@zwave-js/core/safe"; -import type { ZWaveHost, ZWaveValueHost } from "@zwave-js/host/safe"; +import type { + CCEncodingContext, + CCParsingContext, + GetValueDB, +} from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { CCAPI, @@ -19,12 +24,7 @@ import { throwUnsupportedProperty, throwWrongValueType, } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -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, + endpointIndex: this.endpoint.index, sceneId, dimmingDuration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -126,7 +126,7 @@ export class SceneActivationCC extends CommandClass { } // @publicAPI -export interface SceneActivationCCSetOptions extends CCCommandOptions { +export interface SceneActivationCCSetOptions { sceneId: number; dimmingDuration?: Duration | string; } @@ -135,25 +135,32 @@ export interface SceneActivationCCSetOptions extends CCCommandOptions { @useSupervision() export class SceneActivationCCSet extends SceneActivationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SceneActivationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.sceneId = this.payload[0]; - // Per the specs, dimmingDuration is required, but as always the real world is different... - if (this.payload.length >= 2) { - this.dimmingDuration = Duration.parseSet(this.payload[1]); - } + super(options); + this.sceneId = options.sceneId; + this.dimmingDuration = Duration.from(options.dimmingDuration); + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SceneActivationCCSet { + validatePayload(raw.payload.length >= 1); + const sceneId = raw.payload[0]; + validatePayload(sceneId >= 1, sceneId <= 255); - validatePayload(this.sceneId >= 1, this.sceneId <= 255); - } else { - this.sceneId = options.sceneId; - this.dimmingDuration = Duration.from(options.dimmingDuration); + // Per the specs, dimmingDuration is required, but as always the real world is different... + let dimmingDuration: Duration | undefined; + if (raw.payload.length >= 2) { + dimmingDuration = Duration.parseSet(raw.payload[1]); } + + return new SceneActivationCCSet({ + nodeId: ctx.sourceNodeId, + sceneId, + dimmingDuration, + }); } @ccValue(SceneActivationCCValues.sceneId) @@ -162,21 +169,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..80e2dbff7475 100644 --- a/packages/cc/src/cc/SceneActuatorConfigurationCC.ts +++ b/packages/cc/src/cc/SceneActuatorConfigurationCC.ts @@ -6,15 +6,16 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, getCCName, 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"; @@ -30,10 +31,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, } from "../lib/CommandClass"; import { API, @@ -207,16 +208,16 @@ 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, + endpointIndex: this.endpoint.index, sceneId, dimmingDuration: Duration.from(dimmingDuration) ?? new Duration(0, "seconds"), level, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } public async getActive(): Promise< @@ -232,12 +233,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, + endpointIndex: this.endpoint.index, sceneId: 0, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneActuatorConfigurationCCReport >( cc, @@ -272,12 +273,12 @@ export class SceneActuatorConfigurationCCAPI extends CCAPI { ); } - const cc = new SceneActuatorConfigurationCCGet(this.applHost, { + const cc = new SceneActuatorConfigurationCCGet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, sceneId: sceneId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SceneActuatorConfigurationCCReport >( cc, @@ -297,10 +298,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 +313,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", // }); @@ -342,9 +345,7 @@ export class SceneActuatorConfigurationCC extends CommandClass { } // @publicAPI -export interface SceneActuatorConfigurationCCSetOptions - extends CCCommandOptions -{ +export interface SceneActuatorConfigurationCCSetOptions { sceneId: number; dimmingDuration: Duration; level?: number; @@ -356,46 +357,50 @@ export class SceneActuatorConfigurationCCSet extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SceneActuatorConfigurationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload + super(options); + if (options.sceneId < 1 || options.sceneId > 255) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The scene id ${options.sceneId} must be between 1 and 255!`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.sceneId < 1 || options.sceneId > 255) { - throw new ZWaveError( - `The scene id ${options.sceneId} must be between 1 and 255!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.sceneId = options.sceneId; - this.dimmingDuration = options.dimmingDuration; - this.level = options.level; } + this.sceneId = options.sceneId; + this.dimmingDuration = options.dimmingDuration; + this.level = options.level; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): SceneActuatorConfigurationCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SceneActuatorConfigurationCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } public sceneId: number; 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,37 +410,63 @@ export class SceneActuatorConfigurationCCSet } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface SceneActuatorConfigurationCCReportOptions { + sceneId: number; + level?: number; + dimmingDuration?: Duration; +} + @CCCommand(SceneActuatorConfigurationCommand.Report) export class SceneActuatorConfigurationCCReport extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 3); - this.sceneId = this.payload[0]; + super(options); - if (this.sceneId !== 0) { - this.level = this.payload[1]; - this.dimmingDuration = Duration.parseReport(this.payload[2]) + // TODO: Check implementation: + this.sceneId = options.sceneId; + this.level = options.level; + this.dimmingDuration = options.dimmingDuration; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SceneActuatorConfigurationCCReport { + validatePayload(raw.payload.length >= 3); + const sceneId = raw.payload[0]; + + let level: number | undefined; + let dimmingDuration: Duration | undefined; + if (sceneId !== 0) { + level = raw.payload[1]; + dimmingDuration = Duration.parseReport(raw.payload[2]) ?? Duration.unknown(); } + + return new SceneActuatorConfigurationCCReport({ + nodeId: ctx.sourceNodeId, + sceneId, + level, + dimmingDuration, + }); } public readonly sceneId: number; 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 +480,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 +504,7 @@ export class SceneActuatorConfigurationCCReport } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -489,9 +520,7 @@ function testResponseForSceneActuatorConfigurationGet( } // @publicAPI -export interface SceneActuatorConfigurationCCGetOptions - extends CCCommandOptions -{ +export interface SceneActuatorConfigurationCCGetOptions { sceneId: number; } @@ -504,33 +533,37 @@ export class SceneActuatorConfigurationCCGet extends SceneActuatorConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SceneActuatorConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.sceneId = options.sceneId; - } + super(options); + this.sceneId = options.sceneId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): SceneActuatorConfigurationCCGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SceneActuatorConfigurationCCGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..7e500881e206 100644 --- a/packages/cc/src/cc/SceneControllerConfigurationCC.ts +++ b/packages/cc/src/cc/SceneControllerConfigurationCC.ts @@ -1,21 +1,23 @@ import { CommandClasses, Duration, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, getCCName, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -31,10 +33,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -245,7 +248,7 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { if (!this.endpoint.virtual) { const groupCount = SceneControllerConfigurationCC .getGroupCountCached( - this.applHost, + this.host, this.endpoint, ); @@ -265,15 +268,15 @@ export class SceneControllerConfigurationCCAPI extends CCAPI { ); } - const cc = new SceneControllerConfigurationCCSet(this.applHost, { + const cc = new SceneControllerConfigurationCCSet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, groupId, sceneId, 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, + endpointIndex: 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, + endpointIndex: 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,22 +471,18 @@ 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; } } // @publicAPI -export interface SceneControllerConfigurationCCSetOptions - extends CCCommandOptions -{ +export interface SceneControllerConfigurationCCSetOptions { groupId: number; sceneId: number; dimmingDuration?: Duration | string; @@ -491,43 +494,47 @@ export class SceneControllerConfigurationCCSet extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SceneControllerConfigurationCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.groupId = options.groupId; - this.sceneId = options.sceneId; - // if dimmingDuration was missing, use default duration. - this.dimmingDuration = Duration.from(options.dimmingDuration) - ?? Duration.default(); - } + super(options); + this.groupId = options.groupId; + this.sceneId = options.sceneId; + // if dimmingDuration was missing, use default duration. + this.dimmingDuration = Duration.from(options.dimmingDuration) + ?? Duration.default(); + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): SceneControllerConfigurationCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SceneControllerConfigurationCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } public groupId: number; 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, @@ -537,28 +544,52 @@ export class SceneControllerConfigurationCCSet } } +// @publicAPI +export interface SceneControllerConfigurationCCReportOptions { + groupId: number; + sceneId: number; + dimmingDuration: Duration; +} + @CCCommand(SceneControllerConfigurationCommand.Report) export class SceneControllerConfigurationCCReport extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 3); - this.groupId = this.payload[0]; - this.sceneId = this.payload[1]; - this.dimmingDuration = Duration.parseReport(this.payload[2]) + super(options); + + // TODO: Check implementation: + this.groupId = options.groupId; + this.sceneId = options.sceneId; + this.dimmingDuration = options.dimmingDuration; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SceneControllerConfigurationCCReport { + validatePayload(raw.payload.length >= 3); + const groupId = raw.payload[0]; + const sceneId = raw.payload[1]; + const dimmingDuration: Duration = Duration.parseReport(raw.payload[2]) ?? Duration.unknown(); + + return new SceneControllerConfigurationCCReport({ + nodeId: ctx.sourceNodeId, + groupId, + sceneId, + dimmingDuration, + }); } public readonly groupId: number; 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 +597,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, @@ -599,9 +630,7 @@ function testResponseForSceneControllerConfigurationGet( } // @publicAPI -export interface SceneControllerConfigurationCCGetOptions - extends CCCommandOptions -{ +export interface SceneControllerConfigurationCCGetOptions { groupId: number; } @@ -614,33 +643,37 @@ export class SceneControllerConfigurationCCGet extends SceneControllerConfigurationCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SceneControllerConfigurationCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.groupId = options.groupId; - } + super(options); + this.groupId = options.groupId; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): SceneControllerConfigurationCCGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SceneControllerConfigurationCCGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..5ac6c9150d1d 100644 --- a/packages/cc/src/cc/ScheduleEntryLockCC.ts +++ b/packages/cc/src/cc/ScheduleEntryLockCC.ts @@ -1,10 +1,10 @@ import { CommandClasses, - type IZWaveEndpoint, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, type SupervisionResult, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeBitMask, @@ -13,11 +13,11 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core"; -import { type MaybeNotKnown } from "@zwave-js/core/safe"; +import { type EndpointId, type MaybeNotKnown } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host"; import { type AllOrNone, @@ -29,10 +29,10 @@ import { import { validateArgs } from "@zwave-js/transformers"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, } from "../lib/CommandClass"; import { API, @@ -116,7 +116,7 @@ function toPropertyKey( /** Caches information about a schedule */ function persistSchedule( this: ScheduleEntryLockCC, - applHost: ZWaveApplicationHost, + ctx: GetValueDB, scheduleKind: ScheduleEntryLockScheduleKind, userId: number, slotId: number, @@ -134,20 +134,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 +159,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 +177,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 +231,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, + endpointIndex: 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, + endpointIndex: 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 +274,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, + endpointIndex: this.endpoint.index, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCSupportedReport >( cc, @@ -307,7 +307,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC.getNumWeekDaySlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -332,9 +332,9 @@ export class ScheduleEntryLockCCAPI extends CCAPI { } } - const cc = new ScheduleEntryLockCCWeekDayScheduleSet(this.applHost, { + const cc = new ScheduleEntryLockCCWeekDayScheduleSet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...slot, ...(schedule ? { @@ -346,20 +346,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 +369,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 +389,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, + endpointIndex: this.endpoint.index, ...slot, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCWeekDayScheduleReport >( cc, @@ -424,7 +424,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC.getNumYearDaySlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -459,9 +459,9 @@ export class ScheduleEntryLockCCAPI extends CCAPI { } } - const cc = new ScheduleEntryLockCCYearDayScheduleSet(this.applHost, { + const cc = new ScheduleEntryLockCCYearDayScheduleSet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, ...slot, ...(schedule ? { @@ -473,20 +473,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 +496,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 +516,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, + endpointIndex: this.endpoint.index, ...slot, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCYearDayScheduleReport >( cc, @@ -557,7 +557,7 @@ export class ScheduleEntryLockCCAPI extends CCAPI { if (this.isSinglecast()) { const numSlots = ScheduleEntryLockCC .getNumDailyRepeatingSlotsCached( - this.applHost, + this.host, this.endpoint, ); @@ -569,37 +569,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, + endpointIndex: 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 +606,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 +626,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, + endpointIndex: this.endpoint.index, + ...slot, + }); + const result = await this.host.sendCommand< ScheduleEntryLockCCDailyRepeatingScheduleReport >( cc, @@ -661,11 +655,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, + endpointIndex: this.endpoint.index, }); - const result = await this.applHost.sendCommand< + const result = await this.host.sendCommand< ScheduleEntryLockCCTimeOffsetReport >( cc, @@ -686,13 +680,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, + endpointIndex: this.endpoint.index, ...timezone, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -702,24 +696,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 +729,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 +742,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 +753,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } /** @@ -765,18 +761,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 +778,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 +795,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 +816,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 +838,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 +852,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 +877,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 +898,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 +909,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; | ScheduleEntryLockDailyRepeatingSchedule | false > { - return applHost + return ctx .getValueDB(endpoint.nodeId) .getValue( ScheduleEntryLockCCValues.schedule( @@ -932,7 +922,7 @@ daily repeating: ${slotsResp.numDailyRepeatingSlots}`; } // @publicAPI -export interface ScheduleEntryLockCCEnableSetOptions extends CCCommandOptions { +export interface ScheduleEntryLockCCEnableSetOptions { userId: number; enabled: boolean; } @@ -941,33 +931,39 @@ export interface ScheduleEntryLockCCEnableSetOptions extends CCCommandOptions { @useSupervision() export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCEnableSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.enabled = this.payload[1] === 0x01; - } else { - this.userId = options.userId; - this.enabled = options.enabled; - } + super(options); + this.userId = options.userId; + this.enabled = options.enabled; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCEnableSet { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const enabled: boolean = raw.payload[1] === 0x01; + + return new ScheduleEntryLockCCEnableSet({ + nodeId: ctx.sourceNodeId, + userId, + enabled, + }); } 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", @@ -977,9 +973,7 @@ export class ScheduleEntryLockCCEnableSet extends ScheduleEntryLockCC { } // @publicAPI -export interface ScheduleEntryLockCCEnableAllSetOptions - extends CCCommandOptions -{ +export interface ScheduleEntryLockCCEnableAllSetOptions { enabled: boolean; } @@ -987,30 +981,35 @@ export interface ScheduleEntryLockCCEnableAllSetOptions @useSupervision() export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCEnableAllSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.enabled = this.payload[0] === 0x01; - } else { - this.enabled = options.enabled; - } + super(options); + this.enabled = options.enabled; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCEnableAllSet { + validatePayload(raw.payload.length >= 1); + const enabled: boolean = raw.payload[0] === 0x01; + + return new ScheduleEntryLockCCEnableAllSet({ + nodeId: ctx.sourceNodeId, + enabled, + }); } 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", }, @@ -1019,9 +1018,7 @@ export class ScheduleEntryLockCCEnableAllSet extends ScheduleEntryLockCC { } // @publicAPI -export interface ScheduleEntryLockCCSupportedReportOptions - extends CCCommandOptions -{ +export interface ScheduleEntryLockCCSupportedReportOptions { numWeekDaySlots: number; numYearDaySlots: number; numDailyRepeatingSlots?: number; @@ -1030,24 +1027,32 @@ export interface ScheduleEntryLockCCSupportedReportOptions @CCCommand(ScheduleEntryLockCommand.SupportedReport) export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.numWeekDaySlots = this.payload[0]; - this.numYearDaySlots = this.payload[1]; - if (this.payload.length >= 3) { - this.numDailyRepeatingSlots = this.payload[2]; - } - } else { - this.numWeekDaySlots = options.numWeekDaySlots; - this.numYearDaySlots = options.numYearDaySlots; - this.numDailyRepeatingSlots = options.numDailyRepeatingSlots; + super(options); + this.numWeekDaySlots = options.numWeekDaySlots; + this.numYearDaySlots = options.numYearDaySlots; + this.numDailyRepeatingSlots = options.numDailyRepeatingSlots; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCSupportedReport { + validatePayload(raw.payload.length >= 2); + const numWeekDaySlots = raw.payload[0]; + const numYearDaySlots = raw.payload[1]; + let numDailyRepeatingSlots: number | undefined; + if (raw.payload.length >= 3) { + numDailyRepeatingSlots = raw.payload[2]; } + + return new ScheduleEntryLockCCSupportedReport({ + nodeId: ctx.sourceNodeId, + numWeekDaySlots, + numYearDaySlots, + numDailyRepeatingSlots, + }); } @ccValue(ScheduleEntryLockCCValues.numWeekDaySlots) @@ -1057,16 +1062,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 +1081,7 @@ export class ScheduleEntryLockCCSupportedReport extends ScheduleEntryLockCC { this.numDailyRepeatingSlots; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1088,7 +1093,6 @@ export class ScheduleEntryLockCCSupportedGet extends ScheduleEntryLockCC {} /** @publicAPI */ export type ScheduleEntryLockCCWeekDayScheduleSetOptions = - & CCCommandOptions & ScheduleEntryLockSlotId & ( | { @@ -1103,41 +1107,62 @@ export type ScheduleEntryLockCCWeekDayScheduleSetOptions = @useSupervision() export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCWeekDayScheduleSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.action = this.payload[0]; - validatePayload( - this.action === ScheduleEntryLockSetAction.Set - || this.action === ScheduleEntryLockSetAction.Erase, - ); - this.userId = this.payload[1]; - this.slotId = this.payload[2]; - if (this.action === ScheduleEntryLockSetAction.Set) { - validatePayload(this.payload.length >= 8); - this.weekday = this.payload[3]; - this.startHour = this.payload[4]; - this.startMinute = this.payload[5]; - this.stopHour = this.payload[6]; - this.stopMinute = this.payload[7]; - } - } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.action = options.action; - if (options.action === ScheduleEntryLockSetAction.Set) { - this.weekday = options.weekday; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.stopHour = options.stopHour; - this.stopMinute = options.stopMinute; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.action = options.action; + if (options.action === ScheduleEntryLockSetAction.Set) { + this.weekday = options.weekday; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.stopHour = options.stopHour; + this.stopMinute = options.stopMinute; + } + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCWeekDayScheduleSet { + validatePayload(raw.payload.length >= 3); + const action: ScheduleEntryLockSetAction = raw.payload[0]; + + validatePayload( + action === ScheduleEntryLockSetAction.Set + || action === ScheduleEntryLockSetAction.Erase, + ); + const userId = raw.payload[1]; + const slotId = raw.payload[2]; + + if (action !== ScheduleEntryLockSetAction.Set) { + return new ScheduleEntryLockCCWeekDayScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + }); } + + validatePayload(raw.payload.length >= 8); + const weekday: ScheduleEntryLockWeekday = raw.payload[3]; + const startHour = raw.payload[4]; + const startMinute = raw.payload[5]; + const stopHour = raw.payload[6]; + const stopMinute = raw.payload[7]; + + return new ScheduleEntryLockCCWeekDayScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + weekday, + startHour, + startMinute, + stopHour, + stopMinute, + }); } public userId: number; @@ -1151,7 +1176,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 +1190,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 +1221,7 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1204,7 +1229,6 @@ export class ScheduleEntryLockCCWeekDayScheduleSet extends ScheduleEntryLockCC { // @publicAPI export type ScheduleEntryLockCCWeekDayScheduleReportOptions = - & CCCommandOptions & ScheduleEntryLockSlotId & AllOrNone; @@ -1213,42 +1237,76 @@ export class ScheduleEntryLockCCWeekDayScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCWeekDayScheduleReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; - if (this.payload.length >= 7) { - if (this.payload[2] !== 0xff) { - this.weekday = this.payload[2]; - } - if (this.payload[3] !== 0xff) { - this.startHour = this.payload[3]; - } - if (this.payload[4] !== 0xff) { - this.startMinute = this.payload[4]; - } - if (this.payload[5] !== 0xff) { - this.stopHour = this.payload[5]; - } - if (this.payload[6] !== 0xff) { - this.stopMinute = this.payload[6]; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.weekday = options.weekday; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.stopHour = options.stopHour; + this.stopMinute = options.stopMinute; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCWeekDayScheduleReport { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + let ccOptions: ScheduleEntryLockCCWeekDayScheduleReportOptions = { + userId, + slotId, + }; + + let weekday: ScheduleEntryLockWeekday | undefined; + let startHour: number | undefined; + let startMinute: number | undefined; + let stopHour: number | undefined; + let stopMinute: number | undefined; + + if (raw.payload.length >= 7) { + if (raw.payload[2] !== 0xff) { + weekday = raw.payload[2]; } - } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.weekday = options.weekday; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.stopHour = options.stopHour; - this.stopMinute = options.stopMinute; + if (raw.payload[3] !== 0xff) { + startHour = raw.payload[3]; + } + if (raw.payload[4] !== 0xff) { + startMinute = raw.payload[4]; + } + if (raw.payload[5] !== 0xff) { + stopHour = raw.payload[5]; + } + if (raw.payload[6] !== 0xff) { + stopMinute = raw.payload[6]; + } + } + + if ( + weekday != undefined + && startHour != undefined + && startMinute != undefined + && stopHour != undefined + && stopMinute != undefined + ) { + ccOptions = { + ...ccOptions, + weekday, + startHour, + startMinute, + stopHour, + stopMinute, + }; } + + return new ScheduleEntryLockCCWeekDayScheduleReport({ + nodeId: ctx.sourceNodeId, + ...ccOptions, + }); } public userId: number; @@ -1259,12 +1317,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 +1340,7 @@ export class ScheduleEntryLockCCWeekDayScheduleReport return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.userId, this.slotId, @@ -1292,10 +1350,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 +1380,7 @@ export class ScheduleEntryLockCCWeekDayScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1330,40 +1388,45 @@ export class ScheduleEntryLockCCWeekDayScheduleReport // @publicAPI export type ScheduleEntryLockCCWeekDayScheduleGetOptions = - & CCCommandOptions - & ScheduleEntryLockSlotId; + ScheduleEntryLockSlotId; @CCCommand(ScheduleEntryLockCommand.WeekDayScheduleGet) @expectedCCResponse(ScheduleEntryLockCCWeekDayScheduleReport) export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCWeekDayScheduleGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; - } else { - this.userId = options.userId; - this.slotId = options.slotId; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCWeekDayScheduleGet { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + return new ScheduleEntryLockCCWeekDayScheduleGet({ + nodeId: ctx.sourceNodeId, + userId, + slotId, + }); } 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, @@ -1374,7 +1437,6 @@ export class ScheduleEntryLockCCWeekDayScheduleGet extends ScheduleEntryLockCC { /** @publicAPI */ export type ScheduleEntryLockCCYearDayScheduleSetOptions = - & CCCommandOptions & ScheduleEntryLockSlotId & ( | { @@ -1389,51 +1451,77 @@ export type ScheduleEntryLockCCYearDayScheduleSetOptions = @useSupervision() export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCYearDayScheduleSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.action = this.payload[0]; - validatePayload( - this.action === ScheduleEntryLockSetAction.Set - || this.action === ScheduleEntryLockSetAction.Erase, - ); - this.userId = this.payload[1]; - this.slotId = this.payload[2]; - if (this.action === ScheduleEntryLockSetAction.Set) { - validatePayload(this.payload.length >= 13); - this.startYear = this.payload[3]; - this.startMonth = this.payload[4]; - this.startDay = this.payload[5]; - this.startHour = this.payload[6]; - this.startMinute = this.payload[7]; - this.stopYear = this.payload[8]; - this.stopMonth = this.payload[9]; - this.stopDay = this.payload[10]; - this.stopHour = this.payload[11]; - this.stopMinute = this.payload[12]; - } - } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.action = options.action; - if (options.action === ScheduleEntryLockSetAction.Set) { - this.startYear = options.startYear; - this.startMonth = options.startMonth; - this.startDay = options.startDay; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.stopYear = options.stopYear; - this.stopMonth = options.stopMonth; - this.stopDay = options.stopDay; - this.stopHour = options.stopHour; - this.stopMinute = options.stopMinute; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.action = options.action; + if (options.action === ScheduleEntryLockSetAction.Set) { + this.startYear = options.startYear; + this.startMonth = options.startMonth; + this.startDay = options.startDay; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.stopYear = options.stopYear; + this.stopMonth = options.stopMonth; + this.stopDay = options.stopDay; + this.stopHour = options.stopHour; + this.stopMinute = options.stopMinute; + } + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCYearDayScheduleSet { + validatePayload(raw.payload.length >= 3); + const action: ScheduleEntryLockSetAction = raw.payload[0]; + + validatePayload( + action === ScheduleEntryLockSetAction.Set + || action === ScheduleEntryLockSetAction.Erase, + ); + const userId = raw.payload[1]; + const slotId = raw.payload[2]; + + if (action !== ScheduleEntryLockSetAction.Set) { + return new ScheduleEntryLockCCYearDayScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + }); } + + validatePayload(raw.payload.length >= 13); + const startYear = raw.payload[3]; + const startMonth = raw.payload[4]; + const startDay = raw.payload[5]; + const startHour = raw.payload[6]; + const startMinute = raw.payload[7]; + const stopYear = raw.payload[8]; + const stopMonth = raw.payload[9]; + const stopDay = raw.payload[10]; + const stopHour = raw.payload[11]; + const stopMinute = raw.payload[12]; + + return new ScheduleEntryLockCCYearDayScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + startYear, + startMonth, + startDay, + startHour, + startMinute, + stopYear, + stopMonth, + stopDay, + stopHour, + stopMinute, + }); } public userId: number; @@ -1452,7 +1540,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 +1559,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 +1592,7 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1512,7 +1600,6 @@ export class ScheduleEntryLockCCYearDayScheduleSet extends ScheduleEntryLockCC { // @publicAPI export type ScheduleEntryLockCCYearDayScheduleReportOptions = - & CCCommandOptions & ScheduleEntryLockSlotId & AllOrNone; @@ -1521,62 +1608,111 @@ export class ScheduleEntryLockCCYearDayScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCYearDayScheduleReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; - if (this.payload.length >= 12) { - if (this.payload[2] !== 0xff) { - this.startYear = this.payload[2]; - } - if (this.payload[3] !== 0xff) { - this.startMonth = this.payload[3]; - } - if (this.payload[4] !== 0xff) { - this.startDay = this.payload[4]; - } - if (this.payload[5] !== 0xff) { - this.startHour = this.payload[5]; - } - if (this.payload[6] !== 0xff) { - this.startMinute = this.payload[6]; - } - if (this.payload[7] !== 0xff) { - this.stopYear = this.payload[7]; - } - if (this.payload[8] !== 0xff) { - this.stopMonth = this.payload[8]; - } - if (this.payload[9] !== 0xff) { - this.stopDay = this.payload[9]; - } - if (this.payload[10] !== 0xff) { - this.stopHour = this.payload[10]; - } - if (this.payload[11] !== 0xff) { - this.stopMinute = this.payload[11]; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.startYear = options.startYear; + this.startMonth = options.startMonth; + this.startDay = options.startDay; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.stopYear = options.stopYear; + this.stopMonth = options.stopMonth; + this.stopDay = options.stopDay; + this.stopHour = options.stopHour; + this.stopMinute = options.stopMinute; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCYearDayScheduleReport { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + let ccOptions: ScheduleEntryLockCCYearDayScheduleReportOptions = { + userId, + slotId, + }; + + let startYear: number | undefined; + let startMonth: number | undefined; + let startDay: number | undefined; + let startHour: number | undefined; + let startMinute: number | undefined; + let stopYear: number | undefined; + let stopMonth: number | undefined; + let stopDay: number | undefined; + let stopHour: number | undefined; + let stopMinute: number | undefined; + + if (raw.payload.length >= 12) { + if (raw.payload[2] !== 0xff) { + startYear = raw.payload[2]; + } + if (raw.payload[3] !== 0xff) { + startMonth = raw.payload[3]; + } + if (raw.payload[4] !== 0xff) { + startDay = raw.payload[4]; + } + if (raw.payload[5] !== 0xff) { + startHour = raw.payload[5]; + } + if (raw.payload[6] !== 0xff) { + startMinute = raw.payload[6]; + } + if (raw.payload[7] !== 0xff) { + stopYear = raw.payload[7]; + } + if (raw.payload[8] !== 0xff) { + stopMonth = raw.payload[8]; + } + if (raw.payload[9] !== 0xff) { + stopDay = raw.payload[9]; + } + if (raw.payload[10] !== 0xff) { + stopHour = raw.payload[10]; + } + if (raw.payload[11] !== 0xff) { + stopMinute = raw.payload[11]; } - } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.startYear = options.startYear; - this.startMonth = options.startMonth; - this.startDay = options.startDay; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.stopYear = options.stopYear; - this.stopMonth = options.stopMonth; - this.stopDay = options.stopDay; - this.stopHour = options.stopHour; - this.stopMinute = options.stopMinute; } + + if ( + startYear != undefined + && startMonth != undefined + && startDay != undefined + && startHour != undefined + && startMinute != undefined + && stopYear != undefined + && stopMonth != undefined + && stopDay != undefined + && stopHour != undefined + && stopMinute != undefined + ) { + ccOptions = { + ...ccOptions, + startYear, + startMonth, + startDay, + startHour, + startMinute, + stopYear, + stopMonth, + stopDay, + stopHour, + stopMinute, + }; + } + + return new ScheduleEntryLockCCYearDayScheduleReport({ + nodeId: ctx.sourceNodeId, + ...ccOptions, + }); } public userId: number; @@ -1592,12 +1728,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 +1756,7 @@ export class ScheduleEntryLockCCYearDayScheduleReport return true; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ this.userId, this.slotId, @@ -1635,10 +1771,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 +1804,7 @@ export class ScheduleEntryLockCCYearDayScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1676,40 +1812,45 @@ export class ScheduleEntryLockCCYearDayScheduleReport // @publicAPI export type ScheduleEntryLockCCYearDayScheduleGetOptions = - & CCCommandOptions - & ScheduleEntryLockSlotId; + ScheduleEntryLockSlotId; @CCCommand(ScheduleEntryLockCommand.YearDayScheduleGet) @expectedCCResponse(ScheduleEntryLockCCYearDayScheduleReport) export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCYearDayScheduleGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; - } else { - this.userId = options.userId; - this.slotId = options.slotId; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCYearDayScheduleGet { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + return new ScheduleEntryLockCCYearDayScheduleGet({ + nodeId: ctx.sourceNodeId, + userId, + slotId, + }); } 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, @@ -1719,9 +1860,7 @@ export class ScheduleEntryLockCCYearDayScheduleGet extends ScheduleEntryLockCC { } // @publicAPI -export interface ScheduleEntryLockCCTimeOffsetSetOptions - extends CCCommandOptions -{ +export interface ScheduleEntryLockCCTimeOffsetSetOptions { standardOffset: number; dstOffset: number; } @@ -1730,36 +1869,40 @@ export interface ScheduleEntryLockCCTimeOffsetSetOptions @useSupervision() export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCTimeOffsetSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - const { standardOffset, dstOffset } = parseTimezone(this.payload); - this.standardOffset = standardOffset; - this.dstOffset = dstOffset; - } else { - this.standardOffset = options.standardOffset; - this.dstOffset = options.dstOffset; - } + super(options); + this.standardOffset = options.standardOffset; + this.dstOffset = options.dstOffset; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCTimeOffsetSet { + const { standardOffset, dstOffset } = parseTimezone(raw.payload); + + return new ScheduleEntryLockCCTimeOffsetSet({ + nodeId: ctx.sourceNodeId, + standardOffset, + dstOffset, + }); } 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`, @@ -1769,9 +1912,7 @@ export class ScheduleEntryLockCCTimeOffsetSet extends ScheduleEntryLockCC { } // @publicAPI -export interface ScheduleEntryLockCCTimeOffsetReportOptions - extends CCCommandOptions -{ +export interface ScheduleEntryLockCCTimeOffsetReportOptions { standardOffset: number; dstOffset: number; } @@ -1779,36 +1920,40 @@ export interface ScheduleEntryLockCCTimeOffsetReportOptions @CCCommand(ScheduleEntryLockCommand.TimeOffsetReport) export class ScheduleEntryLockCCTimeOffsetReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCTimeOffsetReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - const { standardOffset, dstOffset } = parseTimezone(this.payload); - this.standardOffset = standardOffset; - this.dstOffset = dstOffset; - } else { - this.standardOffset = options.standardOffset; - this.dstOffset = options.dstOffset; - } + super(options); + this.standardOffset = options.standardOffset; + this.dstOffset = options.dstOffset; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCTimeOffsetReport { + const { standardOffset, dstOffset } = parseTimezone(raw.payload); + + return new ScheduleEntryLockCCTimeOffsetReport({ + nodeId: ctx.sourceNodeId, + standardOffset, + dstOffset, + }); } 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`, @@ -1823,7 +1968,6 @@ export class ScheduleEntryLockCCTimeOffsetGet extends ScheduleEntryLockCC {} /** @publicAPI */ export type ScheduleEntryLockCCDailyRepeatingScheduleSetOptions = - & CCCommandOptions & ScheduleEntryLockSlotId & ( | { @@ -1840,44 +1984,67 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCDailyRepeatingScheduleSetOptions, + options: WithAddress< + ScheduleEntryLockCCDailyRepeatingScheduleSetOptions + >, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.action = this.payload[0]; - validatePayload( - this.action === ScheduleEntryLockSetAction.Set - || this.action === ScheduleEntryLockSetAction.Erase, - ); - this.userId = this.payload[1]; - this.slotId = this.payload[2]; - if (this.action === ScheduleEntryLockSetAction.Set) { - validatePayload(this.payload.length >= 8); - this.weekdays = parseBitMask( - this.payload.subarray(3, 4), - ScheduleEntryLockWeekday.Sunday, - ); - this.startHour = this.payload[4]; - this.startMinute = this.payload[5]; - this.durationHour = this.payload[6]; - this.durationMinute = this.payload[7]; - } - } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.action = options.action; - if (options.action === ScheduleEntryLockSetAction.Set) { - this.weekdays = options.weekdays; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.durationHour = options.durationHour; - this.durationMinute = options.durationMinute; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.action = options.action; + if (options.action === ScheduleEntryLockSetAction.Set) { + this.weekdays = options.weekdays; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.durationHour = options.durationHour; + this.durationMinute = options.durationMinute; + } + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCDailyRepeatingScheduleSet { + validatePayload(raw.payload.length >= 3); + const action: ScheduleEntryLockSetAction = raw.payload[0]; + + validatePayload( + action === ScheduleEntryLockSetAction.Set + || action === ScheduleEntryLockSetAction.Erase, + ); + const userId = raw.payload[1]; + const slotId = raw.payload[2]; + + if (action !== ScheduleEntryLockSetAction.Set) { + return new ScheduleEntryLockCCDailyRepeatingScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + }); } + + validatePayload(raw.payload.length >= 8); + const weekdays: ScheduleEntryLockWeekday[] = parseBitMask( + raw.payload.subarray(3, 4), + ScheduleEntryLockWeekday.Sunday, + ); + const startHour = raw.payload[4]; + const startMinute = raw.payload[5]; + const durationHour = raw.payload[6]; + const durationMinute = raw.payload[7]; + + return new ScheduleEntryLockCCDailyRepeatingScheduleSet({ + nodeId: ctx.sourceNodeId, + action, + userId, + slotId, + weekdays, + startHour, + startMinute, + durationHour, + durationMinute, + }); } public userId: number; @@ -1891,7 +2058,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 +2080,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 +2110,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleSet }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -1959,38 +2126,55 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ( - & CCCommandOptions - & ScheduleEntryLockCCDailyRepeatingScheduleReportOptions - ), + options: WithAddress< + ScheduleEntryLockCCDailyRepeatingScheduleReportOptions + >, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + this.weekdays = options.weekdays; + this.startHour = options.startHour; + this.startMinute = options.startMinute; + this.durationHour = options.durationHour; + this.durationMinute = options.durationMinute; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCDailyRepeatingScheduleReport { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + if (raw.payload.length >= 7 && raw.payload[2] !== 0) { // Only parse the schedule if it is present and some weekday is selected - if (this.payload.length >= 7 && this.payload[2] !== 0) { - this.weekdays = parseBitMask( - this.payload.subarray(2, 3), - ScheduleEntryLockWeekday.Sunday, - ); - this.startHour = this.payload[3]; - this.startMinute = this.payload[4]; - this.durationHour = this.payload[5]; - this.durationMinute = this.payload[6]; - } + const weekdays: ScheduleEntryLockWeekday[] = parseBitMask( + raw.payload.subarray(2, 3), + ScheduleEntryLockWeekday.Sunday, + ); + const startHour = raw.payload[3]; + const startMinute = raw.payload[4]; + const durationHour = raw.payload[5]; + const durationMinute = raw.payload[6]; + + return new ScheduleEntryLockCCDailyRepeatingScheduleReport({ + nodeId: ctx.sourceNodeId, + userId, + slotId, + weekdays, + startHour, + startMinute, + durationHour, + durationMinute, + }); } else { - this.userId = options.userId; - this.slotId = options.slotId; - this.weekdays = options.weekdays; - this.startHour = options.startHour; - this.startMinute = options.startMinute; - this.durationHour = options.durationHour; - this.durationMinute = options.durationMinute; + return new ScheduleEntryLockCCDailyRepeatingScheduleReport({ + nodeId: ctx.sourceNodeId, + userId, + slotId, + }); } } @@ -2003,12 +2187,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 +2210,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 +2232,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 +2262,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport }; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -2086,8 +2270,7 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleReport // @publicAPI export type ScheduleEntryLockCCDailyRepeatingScheduleGetOptions = - & CCCommandOptions - & ScheduleEntryLockSlotId; + ScheduleEntryLockSlotId; @CCCommand(ScheduleEntryLockCommand.DailyRepeatingScheduleGet) @expectedCCResponse(ScheduleEntryLockCCDailyRepeatingScheduleReport) @@ -2095,33 +2278,41 @@ export class ScheduleEntryLockCCDailyRepeatingScheduleGet extends ScheduleEntryLockCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ScheduleEntryLockCCDailyRepeatingScheduleGetOptions, + options: WithAddress< + ScheduleEntryLockCCDailyRepeatingScheduleGetOptions + >, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.slotId = this.payload[1]; - } else { - this.userId = options.userId; - this.slotId = options.slotId; - } + super(options); + this.userId = options.userId; + this.slotId = options.slotId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ScheduleEntryLockCCDailyRepeatingScheduleGet { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const slotId = raw.payload[1]; + + return new ScheduleEntryLockCCDailyRepeatingScheduleGet({ + nodeId: ctx.sourceNodeId, + userId, + slotId, + }); } 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..8328e277b7e0 100644 --- a/packages/cc/src/cc/Security2CC.ts +++ b/packages/cc/src/cc/Security2CC.ts @@ -12,6 +12,7 @@ import { SecurityClass, type SecurityManager2, TransmitOptions, + type WithAddress, ZWaveError, ZWaveErrorCodes, decryptAES128CCM, @@ -33,24 +34,23 @@ 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"; import { isArray } from "alcalzone-shared/typeguards"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, type CCResponseRole, CommandClass, - type CommandClassDeserializationOptions, - type CommandClassOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -119,57 +119,41 @@ function getAuthenticationData( unencryptedPayload.copy(ret, offset, 0); return ret; } - function getSecurityManager( - host: ZWaveHost, - destination: MulticastDestination | number, + ownNodeId: number, + securityManagers: SecurityManagers, + otherNodeId: MulticastDestination | number, ): SecurityManager2 | undefined { - const longRange = isLongRangeNodeId(host.ownNodeId) + const longRange = isLongRangeNodeId(ownNodeId) || isLongRangeNodeId( - isArray(destination) ? destination[0] : destination, + isArray(otherNodeId) ? otherNodeId[0] : otherNodeId, ); 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, + sourceNodeId: number, sequenceNumber: number, ): number | undefined { - const securityManager = getSecurityManager(this.host, this.nodeId); - validatePayload.withReason( `Duplicate command (sequence number ${sequenceNumber})`, )( - !securityManager!.isDuplicateSinglecast( - this.nodeId as number, + !securityManager.isDuplicateSinglecast( + sourceNodeId, sequenceNumber, ), ); // Not a duplicate, store it - return securityManager!.storeSequenceNumber( - this.nodeId as number, + return securityManager.storeSequenceNumber( + sourceNodeId, 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; @@ -183,6 +167,297 @@ export interface DecryptionResult { securityClass: SecurityClass | undefined; } +function assertSecurityRX( + ctx: CCParsingContext, +): SecurityManager2 { + if (!ctx.ownNodeId) { + throw new ZWaveError( + `Secure commands (S2) can only be decoded when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + const ret = getSecurityManager(ctx.ownNodeId, ctx, ctx.sourceNodeId); + + if (!ret) { + throw new ZWaveError( + `Secure commands (S2) can only be decoded when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } + + return ret; +} + +function assertSecurityTX( + ctx: CCEncodingContext, + destination: MulticastDestination | number, +): SecurityManager2 { + if (!ctx.ownNodeId) { + throw new ZWaveError( + `Secure commands (S2) can only be sent when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + const ret = getSecurityManager(ctx.ownNodeId, ctx, destination); + + if (!ret) { + throw new ZWaveError( + `Secure commands (S2) can only be sent when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } + + return ret; +} + +function decryptSinglecast( + ctx: CCParsingContext, + securityManager: SecurityManager2, + sendingNodeId: number, + curSequenceNumber: number, + prevSequenceNumber: number, + ciphertext: Buffer, + authData: Buffer, + authTag: Buffer, + spanState: SPANTableEntry & { + type: SPANState.SPAN | SPANState.LocalEI; + }, + extensions: Security2Extension[], +): DecryptionResult { + const decryptWithNonce = (nonce: Buffer) => { + const { keyCCM: key } = securityManager.getKeysForNode( + sendingNodeId, + ); + + const iv = nonce; + return { + key, + iv, + ...decryptAES128CCM(key, iv, ciphertext, authData, authTag), + }; + }; + const getNonceAndDecrypt = () => { + const iv = securityManager.nextNonce(sendingNodeId); + return decryptWithNonce(iv); + }; + + if (spanState.type === SPANState.SPAN) { + // There SHOULD be a shared SPAN between both parties. But experience has shown that both could have + // sent a command at roughly the same time, using the same SPAN for encryption. + // To avoid a nasty desync and both nodes trying to resync at the same time, causing message loss, + // we accept commands encrypted with the previous SPAN under very specific circumstances: + if ( + // The previous SPAN is still known, i.e. the node didn't send another command that was successfully decrypted + !!spanState.currentSPAN + // it is still valid + && spanState.currentSPAN.expires > highResTimestamp() + // The received command is exactly the next, expected one + && prevSequenceNumber != undefined + && curSequenceNumber === ((prevSequenceNumber + 1) & 0xff) + // And in case of a mock-based test, do this only on the controller + && !ctx.__internalIsMockNode + ) { + const nonce = spanState.currentSPAN.nonce; + spanState.currentSPAN = undefined; + + // If we could decrypt this way, we're done... + const result = decryptWithNonce(nonce); + if (result.authOK) { + return { + ...result, + securityClass: spanState.securityClass, + }; + } + // ...otherwise, we need to try the normal way + } else { + // forgetting the current SPAN shouldn't be necessary but better be safe than sorry + spanState.currentSPAN = undefined; + } + + // This can only happen if the security class is known + return { + ...getNonceAndDecrypt(), + securityClass: spanState.securityClass, + }; + } else if (spanState.type === SPANState.LocalEI) { + // We've sent the other our receiver's EI and received its sender's EI, + // meaning we can now establish an SPAN + const senderEI = getSenderEI(extensions); + if (!senderEI) failNoSPAN(); + const receiverEI = spanState.receiverEI; + + // How we do this depends on whether we know the security class of the other node + const isBootstrappingNode = securityManager.tempKeys.has( + sendingNodeId, + ); + if (isBootstrappingNode) { + // We're currently bootstrapping the node, it might be using a temporary key + securityManager.initializeTempSPAN( + sendingNodeId, + senderEI, + receiverEI, + ); + + const ret = getNonceAndDecrypt(); + // Decryption with the temporary key worked + if (ret.authOK) { + return { + ...ret, + securityClass: SecurityClass.Temporary, + }; + } + + // Reset the SPAN state and try with the recently granted security class + securityManager.setSPANState( + sendingNodeId, + spanState, + ); + } + + // When ending up here, one of two situations has occured: + // a) We've taken over an existing network and do not know the node's security class + // b) We know the security class, but we're about to establish a new SPAN. This may happen at a lower + // security class than the one the node normally uses, e.g. when we're being queried for securely + // supported CCs. + // In both cases, we should simply try decoding with multiple security classes, starting from the highest one. + // If this fails, we restore the previous (partial) SPAN state. + + // 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 + ? [ctx.getHighestSecurityClass(sendingNodeId)!] + : securityClassOrder.filter( + (s) => + ctx.hasSecurityClass(sendingNodeId, s) + !== false, + ); + + for (const secClass of possibleSecurityClasses) { + // Skip security classes we don't have keys for + if ( + !securityManager.hasKeysForSecurityClass( + secClass, + ) + ) { + continue; + } + + // Initialize an SPAN with that security class + securityManager.initializeSPAN( + sendingNodeId, + secClass, + senderEI, + receiverEI, + ); + const ret = getNonceAndDecrypt(); + + // It worked, return the result + if (ret.authOK) { + // Also if we weren't sure before, we now know that the security class is granted + if ( + ctx.hasSecurityClass(sendingNodeId, secClass) + === undefined + ) { + ctx.setSecurityClass(sendingNodeId, secClass, true); + } + return { + ...ret, + securityClass: secClass, + }; + } else { + // Reset the SPAN state and try with the next security class + securityManager.setSPANState( + sendingNodeId, + spanState, + ); + } + } + } + + // Nothing worked, fail the decryption + return { + plaintext: Buffer.from([]), + authOK: false, + securityClass: undefined, + }; +} + +function decryptMulticast( + sendingNodeId: number, + securityManager: SecurityManager2, + groupId: number, + ciphertext: Buffer, + authData: Buffer, + authTag: Buffer, +): DecryptionResult { + const iv = securityManager.nextPeerMPAN( + sendingNodeId, + groupId, + ); + const { keyCCM: key } = securityManager.getKeysForNode( + sendingNodeId, + ); + return { + key, + iv, + ...decryptAES128CCM(key, iv, ciphertext, authData, authTag), + // The security class is irrelevant when decrypting multicast commands + securityClass: undefined, + }; +} + +function getDestinationIDTX( + this: Security2CC & { extensions: Security2Extension[] }, +): number { + if (this.isSinglecast()) return this.nodeId; + + const ret = getMulticastGroupId(this.extensions); + if (ret == undefined) { + throw new ZWaveError( + "Multicast Security S2 encapsulation requires the MGRP extension", + ZWaveErrorCodes.Security2CC_MissingExtension, + ); + } + return ret; +} + +function getDestinationIDRX( + ctx: CCParsingContext, + extensions: Security2Extension[], +): number { + if (ctx.frameType === "singlecast") { + return ctx.ownNodeId; + } + + const ret = getMulticastGroupId(extensions); + if (ret == undefined) { + throw new ZWaveError( + "Multicast Security S2 encapsulation requires the MGRP extension", + ZWaveErrorCodes.Security2CC_MissingExtension, + ); + } + return ret; +} + +function getMulticastGroupId( + extensions: Security2Extension[], +): number | undefined { + const mgrpExtension = extensions.find( + (e) => e instanceof MGRPExtension, + ); + return mgrpExtension?.groupId; +} + +/** Returns the Sender's Entropy Input if this command contains an SPAN extension */ +function getSenderEI(extensions: Security2Extension[]): Buffer | undefined { + const spanExtension = extensions.find( + (e) => e instanceof SPANExtension, + ); + return spanExtension?.senderEI; +} + // Encapsulation CCs are used internally and too frequently that we // want to pay the cost of validating each call /* eslint-disable @zwave-js/ccapi-validate-args */ @@ -207,7 +482,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 +498,16 @@ export class Security2CCAPI extends CCAPI { this.endpoint.nodeId, ); - const cc = new Security2CCNonceReport(this.applHost, { + const cc = new Security2CCNonceReport({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, 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 +544,15 @@ export class Security2CCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); - const cc = new Security2CCNonceReport(this.applHost, { + const cc = new Security2CCNonceReport({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, 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 +587,9 @@ export class Security2CCAPI extends CCAPI { this.assertPhysicalEndpoint(this.endpoint); - const cc = new Security2CCMessageEncapsulation(this.applHost, { + const cc = new Security2CCMessageEncapsulation({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, extensions: [ new MPANExtension({ groupId, @@ -323,7 +599,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 +637,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, + endpointIndex: 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 +681,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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -437,13 +722,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, + endpointIndex: 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 +737,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, + endpointIndex: 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 +755,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, + endpointIndex: 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 +773,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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, failType, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async sendPublicKey( @@ -518,13 +803,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, + endpointIndex: this.endpoint.index, includingNode, publicKey, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async requestNetworkKey( @@ -535,12 +820,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, + endpointIndex: this.endpoint.index, requestedKey: securityClass, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async sendNetworkKey( @@ -552,13 +837,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, + endpointIndex: 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 +852,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, + endpointIndex: this.endpoint.index, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async confirmKeyVerification(): Promise { @@ -580,13 +865,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, + endpointIndex: 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 +884,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, + endpointIndex: this.endpoint.index, keyVerified: false, keyRequestComplete: true, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -614,18 +899,24 @@ 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)!; + 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 +935,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 +959,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 +972,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 +1009,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 +1022,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 +1041,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 +1068,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 +1085,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 +1111,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 +1135,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 +1191,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,7 +1225,7 @@ export class Security2CC extends CommandClass { nodeId = cc.nodeId as number; } - const ret = new Security2CCMessageEncapsulation(host, { + const ret = new Security2CCMessageEncapsulation({ nodeId, encapsulated: cc, securityClass: options?.securityClass, @@ -950,10 +1242,28 @@ export class Security2CC extends CommandClass { } } +function failNoSPAN(): never { + validatePayload.fail(ZWaveErrorCodes.Security2CC_NoSPAN); +} + +function failNoMPAN(): never { + validatePayload.fail(ZWaveErrorCodes.Security2CC_NoMPAN); +} + +// @publicAPI +export type MulticastContext = + | { + isMulticast: true; + groupId: number; + } + | { + isMulticast: false; + groupId?: number; + }; + // @publicAPI -export interface Security2CCMessageEncapsulationOptions - extends CCCommandOptions -{ +export interface Security2CCMessageEncapsulationOptions { + sequenceNumber?: number; /** Can be used to override the default security class for the command */ securityClass?: SecurityClass; extensions?: Security2Extension[]; @@ -1001,25 +1311,6 @@ function testCCResponseForMessageEncapsulation( } } -function failNoSPAN(): never { - validatePayload.fail(ZWaveErrorCodes.Security2CC_NoSPAN); -} - -function failNoMPAN(): never { - validatePayload.fail(ZWaveErrorCodes.Security2CC_NoMPAN); -} - -// @publicAPI -export type MulticastContext = - | { - isMulticast: true; - groupId: number; - } - | { - isMulticast: false; - groupId?: number; - }; - @CCCommand(Security2Command.MessageEncapsulation) @expectedCCResponse( getCCResponseForMessageEncapsulation, @@ -1027,363 +1318,377 @@ export type MulticastContext = ) export class Security2CCMessageEncapsulation extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCMessageEncapsulationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; + if (!options.encapsulated && !options.extensions?.length) { + throw new ZWaveError( + "Security S2 encapsulation requires an encapsulated CC and/or extensions", + ZWaveErrorCodes.Argument_Invalid, + ); + } - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - // Check the sequence number to avoid duplicates - this._sequenceNumber = this.payload[0]; - const sendingNodeId = this.nodeId as number; + this.sequenceNumber = options.sequenceNumber; - // Ensure the node has a security class - const securityClass = this.host.getHighestSecurityClass( - sendingNodeId, - ); - validatePayload.withReason("No security class granted")( - securityClass !== SecurityClass.None, - ); + this.securityClass = options.securityClass; + if (options.encapsulated) { + this.encapsulated = options.encapsulated; + options.encapsulated.encapsulatingCC = this as any; + } - const hasExtensions = !!(this.payload[1] & 0b1); - const hasEncryptedExtensions = !!(this.payload[1] & 0b10); + this.verifyDelivery = options.verifyDelivery !== false; + + this.extensions = options.extensions ?? []; + if ( + typeof this.nodeId !== "number" + && !this.extensions.some((e) => e instanceof MGRPExtension) + ) { + throw new ZWaveError( + "Multicast Security S2 encapsulation requires the MGRP extension", + ZWaveErrorCodes.Security2CC_MissingExtension, + ); + } + } - let offset = 2; - this.extensions = []; - let mustDiscardCommand = false; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCMessageEncapsulation { + const securityManager = assertSecurityRX(ctx); - const parseExtensions = (buffer: Buffer, wasEncrypted: boolean) => { - while (true) { - if (buffer.length < offset + 2) { - // An S2 extension was expected, but the buffer is too short - mustDiscardCommand = true; - return; - } + validatePayload(raw.payload.length >= 2); + // Check the sequence number to avoid duplicates + const sequenceNumber: number | undefined = raw.payload[0]; - // The length field could be too large, which would cause part of the actual ciphertext - // to be ignored. Try to avoid this for known extensions by checking the actual and expected length. - const { actual: actualLength, expected: expectedLength } = - Security2Extension - .getExtensionLength( - buffer.subarray(offset), - ); - - // Parse the extension using the expected length if possible - const extensionLength = expectedLength ?? actualLength; - if (extensionLength < 2) { - // An S2 extension was expected, but the length is too short - mustDiscardCommand = true; - return; - } else if ( - extensionLength - > buffer.length - - offset - - (wasEncrypted - ? 0 - : SECURITY_S2_AUTH_TAG_LENGTH) - ) { - // The supposed length is longer than the space the extensions may occupy - mustDiscardCommand = true; - return; - } + // Ensure the node has a security class + validatePayload.withReason("No security class granted")( + ctx.getHighestSecurityClass( + ctx.sourceNodeId, + ) !== SecurityClass.None, + ); - const extensionData = buffer.subarray( - offset, - offset + extensionLength, - ); - offset += extensionLength; - - const ext = Security2Extension.from(extensionData); - - switch (validateS2Extension(ext, wasEncrypted)) { - case ValidateS2ExtensionResult.OK: - if ( - expectedLength != undefined - && actualLength !== expectedLength - ) { - // The extension length field does not match, ignore the extension - } else { - this.extensions.push(ext); - } - break; - case ValidateS2ExtensionResult.DiscardExtension: - // Do nothing - break; - case ValidateS2ExtensionResult.DiscardCommand: - mustDiscardCommand = true; - break; - } + const hasExtensions = !!(raw.payload[1] & 0b1); + const hasEncryptedExtensions = !!(raw.payload[1] & 0b10); - // Check if that was the last extension - if (!ext.moreToFollow) break; + let offset = 2; + const extensions: Security2Extension[] = []; + let mustDiscardCommand = false; + + const parseExtensions = (buffer: Buffer, wasEncrypted: boolean) => { + while (true) { + if (buffer.length < offset + 2) { + // An S2 extension was expected, but the buffer is too short + mustDiscardCommand = true; + return; } - }; - if (hasExtensions) parseExtensions(this.payload, false); - const ctx = ((): MulticastContext => { - const multicastGroupId = this.getMulticastGroupId(); - if ( - options.frameType === "multicast" - || options.frameType === "broadcast" - ) { - if (multicastGroupId == undefined) { - validatePayload.fail( - "Multicast frames without MGRP extension", + // The length field could be too large, which would cause part of the actual ciphertext + // to be ignored. Try to avoid this for known extensions by checking the actual and expected length. + const { actual: actualLength, expected: expectedLength } = + Security2Extension + .getExtensionLength( + buffer.subarray(offset), ); - } - return { - isMulticast: true, - groupId: multicastGroupId, - }; - } else { - return { isMulticast: false, groupId: multicastGroupId }; - } - })(); - - // If a command is to be discarded before decryption, - // we still need to increment the SPAN or MPAN state - if (mustDiscardCommand) { - if (ctx.isMulticast) { - this.securityManager.nextPeerMPAN( - sendingNodeId, - ctx.groupId, - ); - } else { - this.securityManager.nextNonce(sendingNodeId); + + // Parse the extension using the expected length if possible + const extensionLength = expectedLength ?? actualLength; + if (extensionLength < 2) { + // An S2 extension was expected, but the length is too short + mustDiscardCommand = true; + return; + } else if ( + extensionLength + > buffer.length + - offset + - (wasEncrypted + ? 0 + : SECURITY_S2_AUTH_TAG_LENGTH) + ) { + // The supposed length is longer than the space the extensions may occupy + mustDiscardCommand = true; + return; } - validatePayload.fail( - "Invalid S2 extension", - ); - } - let prevSequenceNumber: number | undefined; - let mpanState: - | ReturnType - | undefined; - if (ctx.isMulticast) { - mpanState = this.securityManager.getPeerMPAN( - sendingNodeId, - ctx.groupId, - ); - } else { - // Don't accept duplicate Singlecast commands - prevSequenceNumber = validateSequenceNumber.call( - this, - this._sequenceNumber, + const extensionData = buffer.subarray( + offset, + offset + extensionLength, ); - - // When a node receives a singlecast message after a multicast group was marked out of sync, - // it must forget about the group. - if (ctx.groupId == undefined) { - this.securityManager.resetOutOfSyncMPANs( - sendingNodeId, - ); + offset += extensionLength; + + const ext = Security2Extension.from(extensionData); + + switch (validateS2Extension(ext, wasEncrypted)) { + case ValidateS2ExtensionResult.OK: + if ( + expectedLength != undefined + && actualLength !== expectedLength + ) { + // The extension length field does not match, ignore the extension + } else { + extensions.push(ext); + } + break; + case ValidateS2ExtensionResult.DiscardExtension: + // Do nothing + break; + case ValidateS2ExtensionResult.DiscardCommand: + mustDiscardCommand = true; + break; } - } - - const unencryptedPayload = this.payload.subarray(0, offset); - const ciphertext = this.payload.subarray( - offset, - -SECURITY_S2_AUTH_TAG_LENGTH, - ); - const authTag = this.payload.subarray(-SECURITY_S2_AUTH_TAG_LENGTH); - this.authTag = authTag; - - const messageLength = super.computeEncapsulationOverhead() - + this.payload.length; - const authData = getAuthenticationData( - sendingNodeId, - this.getDestinationIDRX(), - this.host.homeId, - messageLength, - unencryptedPayload, - ); + // Check if that was the last extension + if (!ext.moreToFollow) break; + } + }; + if (hasExtensions) parseExtensions(raw.payload, false); - let decrypt: () => DecryptionResult; - if (ctx.isMulticast) { - // For incoming multicast commands, make sure we have an MPAN - if (mpanState?.type !== MPANState.MPAN) { - // If we don't, mark the MPAN as out of sync, so we can respond accordingly on the singlecast followup - this.securityManager.storePeerMPAN( - sendingNodeId, - ctx.groupId, - { type: MPANState.OutOfSync }, + const mcctx = ((): MulticastContext => { + const multicastGroupId = getMulticastGroupId(extensions); + if ( + ctx.frameType === "multicast" || ctx.frameType === "broadcast" + ) { + if (multicastGroupId == undefined) { + validatePayload.fail( + "Multicast frames without MGRP extension", ); - failNoMPAN(); } - - decrypt = () => - this.decryptMulticast( - sendingNodeId, - ctx.groupId, - ciphertext, - authData, - authTag, - ); + return { + isMulticast: true, + groupId: multicastGroupId, + }; } else { - // Decrypt payload and verify integrity - const spanState = this.securityManager.getSPANState( - sendingNodeId, + return { isMulticast: false, groupId: multicastGroupId }; + } + })(); + + // If a command is to be discarded before decryption, + // we still need to increment the SPAN or MPAN state + if (mustDiscardCommand) { + if (mcctx.isMulticast) { + securityManager.nextPeerMPAN( + ctx.sourceNodeId, + mcctx.groupId, ); + } else { + securityManager.nextNonce(ctx.sourceNodeId); + } + validatePayload.fail( + "Invalid S2 extension", + ); + } - // If we are not able to establish an SPAN yet, fail the decryption - if (spanState.type === SPANState.None) { - failNoSPAN(); - } else if (spanState.type === SPANState.RemoteEI) { - // TODO: The specs are not clear how to handle this case - // For now, do the same as if we didn't have any EI - failNoSPAN(); - } + let prevSequenceNumber: number | undefined; + let mpanState: + | ReturnType + | undefined; + if (mcctx.isMulticast) { + mpanState = securityManager.getPeerMPAN( + ctx.sourceNodeId, + mcctx.groupId, + ); + } else { + // Don't accept duplicate Singlecast commands + prevSequenceNumber = validateSequenceNumber( + securityManager, + ctx.sourceNodeId, + sequenceNumber, + ); - decrypt = () => - this.decryptSinglecast( - sendingNodeId, - prevSequenceNumber!, - ciphertext, - authData, - authTag, - spanState, - ); + // When a node receives a singlecast message after a multicast group was marked out of sync, + // it must forget about the group. + if (mcctx.groupId == undefined) { + securityManager.resetOutOfSyncMPANs( + ctx.sourceNodeId, + ); } + } - let plaintext: Buffer | undefined; - let authOK = false; - let key: Buffer | undefined; - let iv: Buffer | undefined; - let decryptionSecurityClass: SecurityClass | undefined; - - // If the Receiver is unable to authenticate the singlecast message with the current SPAN, - // the Receiver SHOULD try decrypting the message with one or more of the following SPAN values, - // stopping when decryption is successful or the maximum number of iterations is reached. - - // If the Receiver is unable to decrypt the S2 MC frame with the current MPAN, the Receiver MAY try - // decrypting the frame with one or more of the subsequent MPAN values, stopping when decryption is - // successful or the maximum number of iterations is reached. - const decryptAttempts = ctx.isMulticast - ? MAX_DECRYPT_ATTEMPTS_MULTICAST - : ctx.groupId != undefined - ? MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP - : MAX_DECRYPT_ATTEMPTS_SINGLECAST; - - for (let i = 0; i < decryptAttempts; i++) { - ({ - plaintext, - authOK, - key, - iv, - securityClass: decryptionSecurityClass, - } = decrypt()); - if (!!authOK && !!plaintext) break; - // No need to try further SPANs if we just got the sender's EI - if (!!this.getSenderEI()) break; - } + const unencryptedPayload = raw.payload.subarray(0, offset); + const ciphertext = raw.payload.subarray( + offset, + -SECURITY_S2_AUTH_TAG_LENGTH, + ); + const authTag = raw.payload.subarray(-SECURITY_S2_AUTH_TAG_LENGTH); + const messageLength = + 2 /* CommandClass.computeEncapsulationOverhead() */ + + raw.payload.length; - // If authentication fails, do so with an error code that instructs the - // applHost to tell the node we have no nonce - if (!authOK || !plaintext) { - if (ctx.isMulticast) { - // Mark the MPAN as out of sync - this.securityManager.storePeerMPAN( - sendingNodeId, - ctx.groupId, - { type: MPANState.OutOfSync }, - ); - validatePayload.fail( - ZWaveErrorCodes.Security2CC_CannotDecodeMulticast, - ); - } else { - validatePayload.fail( - ZWaveErrorCodes.Security2CC_CannotDecode, - ); - } - } else if (!ctx.isMulticast && ctx.groupId != undefined) { - // After reception of a singlecast followup, the MPAN state must be increased - this.securityManager.tryIncrementPeerMPAN( - sendingNodeId, - ctx.groupId, + const authData = getAuthenticationData( + ctx.sourceNodeId, + getDestinationIDRX(ctx, extensions), + ctx.homeId, + messageLength, + unencryptedPayload, + ); + + let decrypt: () => DecryptionResult; + if (mcctx.isMulticast) { + // For incoming multicast commands, make sure we have an MPAN + if (mpanState?.type !== MPANState.MPAN) { + // If we don't, mark the MPAN as out of sync, so we can respond accordingly on the singlecast followup + securityManager.storePeerMPAN( + ctx.sourceNodeId, + mcctx.groupId, + { type: MPANState.OutOfSync }, ); + failNoMPAN(); } - // Remember which security class was used to decrypt this message, so we can discard it later - this.securityClass = decryptionSecurityClass; - - offset = 0; - if (hasEncryptedExtensions) parseExtensions(plaintext, true); + decrypt = () => + decryptMulticast( + ctx.sourceNodeId, + securityManager, + mcctx.groupId, + ciphertext, + authData, + authTag, + ); + } else { + // Decrypt payload and verify integrity + const spanState = securityManager.getSPANState( + ctx.sourceNodeId, + ); - // Before we can continue, check if the command must be discarded - if (mustDiscardCommand) { - validatePayload.fail("Invalid extension"); + // If we are not able to establish an SPAN yet, fail the decryption + if (spanState.type === SPANState.None) { + failNoSPAN(); + } else if (spanState.type === SPANState.RemoteEI) { + // TODO: The specs are not clear how to handle this case + // For now, do the same as if we didn't have any EI + failNoSPAN(); } - // If the MPAN extension was received, store the MPAN - if (!ctx.isMulticast) { - const mpanExtension = this.getMPANExtension(); - if (mpanExtension) { - this.securityManager.storePeerMPAN( - sendingNodeId, - mpanExtension.groupId, - { - type: MPANState.MPAN, - currentMPAN: mpanExtension.innerMPANState, - }, - ); - } - } + decrypt = () => + decryptSinglecast( + ctx, + securityManager, + ctx.sourceNodeId, + sequenceNumber, + prevSequenceNumber!, + ciphertext, + authData, + authTag, + spanState, + extensions, + ); + } - // Not every S2 message includes an encapsulated CC - const decryptedCCBytes = plaintext.subarray(offset); - if (decryptedCCBytes.length > 0) { - // 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, { - data: decryptedCCBytes, - fromEncapsulation: true, - encapCC: this, - frameType: options.frameType, - }); - } - this.plaintext = decryptedCCBytes; - this.key = key; - this.iv = iv; - } else { - if (!options.encapsulated && !options.extensions?.length) { - throw new ZWaveError( - "Security S2 encapsulation requires an encapsulated CC and/or extensions", - ZWaveErrorCodes.Argument_Invalid, + let plaintext: Buffer | undefined; + let authOK = false; + let key: Buffer | undefined; + let iv: Buffer | undefined; + let decryptionSecurityClass: SecurityClass | undefined; + + // If the Receiver is unable to authenticate the singlecast message with the current SPAN, + // the Receiver SHOULD try decrypting the message with one or more of the following SPAN values, + // stopping when decryption is successful or the maximum number of iterations is reached. + + // If the Receiver is unable to decrypt the S2 MC frame with the current MPAN, the Receiver MAY try + // decrypting the frame with one or more of the subsequent MPAN values, stopping when decryption is + // successful or the maximum number of iterations is reached. + const decryptAttempts = mcctx.isMulticast + ? MAX_DECRYPT_ATTEMPTS_MULTICAST + : mcctx.groupId != undefined + ? MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP + : MAX_DECRYPT_ATTEMPTS_SINGLECAST; + + for (let i = 0; i < decryptAttempts; i++) { + ({ + plaintext, + authOK, + key, + iv, + securityClass: decryptionSecurityClass, + } = decrypt()); + if (!!authOK && !!plaintext) break; + // No need to try further SPANs if we just got the sender's EI + if (!!getSenderEI(extensions)) break; + } + + // If authentication fails, do so with an error code that instructs the + // applHost to tell the node we have no nonce + if (!authOK || !plaintext) { + if (mcctx.isMulticast) { + // Mark the MPAN as out of sync + securityManager.storePeerMPAN( + ctx.sourceNodeId, + mcctx.groupId, + { type: MPANState.OutOfSync }, + ); + validatePayload.fail( + ZWaveErrorCodes.Security2CC_CannotDecodeMulticast, + ); + } else { + validatePayload.fail( + ZWaveErrorCodes.Security2CC_CannotDecode, ); } + } else if (!mcctx.isMulticast && mcctx.groupId != undefined) { + // After reception of a singlecast followup, the MPAN state must be increased + securityManager.tryIncrementPeerMPAN( + ctx.sourceNodeId, + mcctx.groupId, + ); + } - this.securityClass = options.securityClass; - if (options.encapsulated) { - this.encapsulated = options.encapsulated; - options.encapsulated.encapsulatingCC = this as any; - } + // Remember which security class was used to decrypt this message, so we can discard it later + const securityClass: SecurityClass | undefined = + decryptionSecurityClass; - this.verifyDelivery = options.verifyDelivery !== false; + offset = 0; + if (hasEncryptedExtensions) parseExtensions(plaintext, true); - this.extensions = options.extensions ?? []; - if ( - typeof this.nodeId !== "number" - && !this.extensions.some((e) => e instanceof MGRPExtension) - ) { - throw new ZWaveError( - "Multicast Security S2 encapsulation requires the MGRP extension", - ZWaveErrorCodes.Security2CC_MissingExtension, + // Before we can continue, check if the command must be discarded + if (mustDiscardCommand) { + validatePayload.fail("Invalid extension"); + } + + // If the MPAN extension was received, store the MPAN + if (!mcctx.isMulticast) { + const mpanExtension = extensions.find((e) => + e instanceof MPANExtension + ); + if (mpanExtension) { + securityManager.storePeerMPAN( + ctx.sourceNodeId, + mpanExtension.groupId, + { + type: MPANState.MPAN, + currentMPAN: mpanExtension.innerMPANState, + }, ); } } + + // Not every S2 message includes an encapsulated CC + const decryptedCCBytes = plaintext.subarray(offset); + let encapsulated: CommandClass | undefined; + if (decryptedCCBytes.length > 0) { + // make sure this contains a complete CC command that's worth splitting + validatePayload(decryptedCCBytes.length >= 2); + // and deserialize the CC + encapsulated = CommandClass.parse(decryptedCCBytes, ctx); + } + + const ret = new Security2CCMessageEncapsulation({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + securityClass, + extensions, + encapsulated, + }); + + // Remember for debugging purposes + ret.key = key; + ret.iv = iv; + ret.authData = authData; + ret.authTag = authTag; + ret.plaintext = decryptedCCBytes; + + return ret; } - private securityManager: SecurityManager2; public readonly securityClass?: SecurityClass; // Only used for testing/debugging purposes @@ -1396,26 +1701,25 @@ export class Security2CCMessageEncapsulation extends Security2CC { public readonly verifyDelivery: boolean = true; - private _sequenceNumber: number | undefined; - /** - * Return the sequence number of this command. - * - * **WARNING:** If the sequence number hasn't been set before, this will create a new one. - * When sending messages, this should only happen immediately before serializing. - */ - public get sequenceNumber(): number { - if (this._sequenceNumber == undefined) { + public sequenceNumber: number | undefined; + private ensureSequenceNumber( + securityManager: SecurityManager2, + ): asserts this is this & { + sequenceNumber: number; + } { + if (this.sequenceNumber == undefined) { if (this.isSinglecast()) { - this._sequenceNumber = this.securityManager - .nextSequenceNumber(this.nodeId); - } else { - const groupId = this.getDestinationIDTX(); - return this.securityManager.nextMulticastSequenceNumber( - groupId, + this.sequenceNumber = securityManager.nextSequenceNumber( + this.nodeId, ); + } else { + const groupId = getDestinationIDTX.call(this); + this.sequenceNumber = securityManager + .nextMulticastSequenceNumber( + groupId, + ); } } - return this._sequenceNumber; } public encapsulated?: CommandClass; @@ -1423,50 +1727,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { public override prepareRetransmission(): void { super.prepareRetransmission(); - this._sequenceNumber = undefined; - } - - private getDestinationIDTX(): number { - if (this.isSinglecast()) return this.nodeId; - - const ret = this.getMulticastGroupId(); - if (ret == undefined) { - throw new ZWaveError( - "Multicast Security S2 encapsulation requires the MGRP extension", - ZWaveErrorCodes.Security2CC_MissingExtension, - ); - } - return ret; - } - - private getDestinationIDRX(): number { - if (this.isSinglecast()) return this.host.ownNodeId; - - const ret = this.getMulticastGroupId(); - if (ret == undefined) { - throw new ZWaveError( - "Multicast Security S2 encapsulation requires the MGRP extension", - ZWaveErrorCodes.Security2CC_MissingExtension, - ); - } - return ret; - } - - private getMGRPExtension(): MGRPExtension | undefined { - return this.extensions.find( - (e) => e instanceof MGRPExtension, - ); - } - - public getMulticastGroupId(): number | undefined { - const mgrpExtension = this.getMGRPExtension(); - return mgrpExtension?.groupId; - } - - private getMPANExtension(): MPANExtension | undefined { - return this.extensions.find( - (e) => e instanceof MPANExtension, - ); + this.sequenceNumber = undefined; } public hasMOSExtension(): boolean { @@ -1475,17 +1736,22 @@ export class Security2CCMessageEncapsulation extends Security2CC { /** Returns the Sender's Entropy Input if this command contains an SPAN extension */ public getSenderEI(): Buffer | undefined { - const spanExtension = this.extensions.find( - (e) => e instanceof SPANExtension, - ); - return spanExtension?.senderEI; + return getSenderEI(this.extensions); } - private maybeAddSPANExtension(): void { + /** Returns the multicast group ID if this command contains an MGRP extension */ + public getMulticastGroupId(): number | undefined { + return getMulticastGroupId(this.extensions); + } + + private maybeAddSPANExtension( + ctx: CCEncodingContext, + securityManager: SecurityManager2, + ): void { if (!this.isSinglecast()) return; const receiverNodeId: number = this.nodeId; - const spanState = this.securityManager.getSPANState( + const spanState = securityManager.getSPANState( receiverNodeId, ); if ( @@ -1500,7 +1766,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { } else if (spanState.type === SPANState.RemoteEI) { // We have the receiver's EI, generate our input and send it over // With both, we can create an SPAN - const senderEI = this.securityManager.generateNonce( + const senderEI = securityManager.generateNonce( undefined, ); const receiverEI = spanState.receiverEI; @@ -1509,16 +1775,16 @@ export class Security2CCMessageEncapsulation extends Security2CC { // specific command specifies a security class if ( this.securityClass == undefined - && this.securityManager.tempKeys.has(receiverNodeId) + && securityManager.tempKeys.has(receiverNodeId) ) { - this.securityManager.initializeTempSPAN( + securityManager.initializeTempSPAN( receiverNodeId, senderEI, receiverEI, ); } else { const securityClass = this.securityClass - ?? this.host.getHighestSecurityClass(receiverNodeId); + ?? ctx.getHighestSecurityClass(receiverNodeId); if (securityClass == undefined) { throw new ZWaveError( @@ -1526,7 +1792,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { ZWaveErrorCodes.Security2CC_NoSPAN, ); } - this.securityManager.initializeSPAN( + securityManager.initializeSPAN( receiverNodeId, securityClass, senderEI, @@ -1547,9 +1813,12 @@ export class Security2CCMessageEncapsulation extends Security2CC { } } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { + const securityManager = assertSecurityTX(ctx, this.nodeId); + this.ensureSequenceNumber(securityManager); + // Include Sender EI in the command if we only have the receiver's EI - this.maybeAddSPANExtension(); + this.maybeAddSPANExtension(ctx, securityManager); const unencryptedExtensions = this.extensions.filter( (e) => !e.isEncrypted(), @@ -1568,7 +1837,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) @@ -1577,13 +1847,15 @@ export class Security2CCMessageEncapsulation extends Security2CC { ]); // Generate the authentication data for CCM encryption - const destinationTag = this.getDestinationIDTX(); + const destinationTag = getDestinationIDTX.call( + this as Security2CCMessageEncapsulation, + ); const messageLength = this.computeEncapsulationOverhead() + serializedCC.length; const authData = getAuthenticationData( - this.host.ownNodeId, + ctx.ownNodeId, destinationTag, - this.host.homeId, + ctx.homeId, messageLength, unencryptedPayload, ); @@ -1595,18 +1867,18 @@ export class Security2CCMessageEncapsulation extends Security2CC { // Singlecast: // Generate a nonce for encryption, and remember it to attempt decryption // of potential in-flight messages from the target node. - iv = this.securityManager.nextNonce(this.nodeId, true); + iv = securityManager.nextNonce(this.nodeId, true); const { keyCCM } = // Prefer the overridden security class if it was given this.securityClass != undefined - ? this.securityManager.getKeysForSecurityClass( + ? securityManager.getKeysForSecurityClass( this.securityClass, ) - : this.securityManager.getKeysForNode(this.nodeId); + : securityManager.getKeysForNode(this.nodeId); key = keyCCM; } else { // Multicast: - const keyAndIV = this.securityManager.getMulticastKeyAndIV( + const keyAndIV = securityManager.getMulticastKeyAndIV( destinationTag, ); key = keyAndIV.key; @@ -1633,7 +1905,7 @@ export class Security2CCMessageEncapsulation extends Security2CC { ciphertextPayload, authTag, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -1654,9 +1926,9 @@ export class Security2CCMessageEncapsulation extends Security2CC { ); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { const message: MessageRecord = { - "sequence number": this.sequenceNumber, + "sequence number": this.sequenceNumber ?? "(not set)", }; if (this.extensions.length > 0) { message.extensions = this.extensions @@ -1687,307 +1959,122 @@ export class Security2CCMessageEncapsulation extends Security2CC { } } - if (this.isSinglecast()) { - // TODO: This is ugly, we should probably do this in the constructor or so - let securityClass = this.securityClass; - if (securityClass == undefined) { - const spanState = this.securityManager.getSPANState( - this.nodeId, - ); - if (spanState.type === SPANState.SPAN) { - securityClass = spanState.securityClass; - } - } - - if (securityClass != undefined) { - message["security class"] = getEnumMemberName( - SecurityClass, - securityClass, - ); - } - } - - return { - ...super.toLogEntry(host), - message, - }; - } - - private decryptSinglecast( - sendingNodeId: number, - prevSequenceNumber: number, - ciphertext: Buffer, - authData: Buffer, - authTag: Buffer, - spanState: SPANTableEntry & { - type: SPANState.SPAN | SPANState.LocalEI; - }, - ): DecryptionResult { - const decryptWithNonce = (nonce: Buffer) => { - const { keyCCM: key } = this.securityManager.getKeysForNode( - sendingNodeId, + if (this.securityClass != undefined) { + message["security class"] = getEnumMemberName( + SecurityClass, + this.securityClass, ); - - const iv = nonce; - return { - key, - iv, - ...decryptAES128CCM(key, iv, ciphertext, authData, authTag), - }; - }; - const getNonceAndDecrypt = () => { - const iv = this.securityManager.nextNonce(sendingNodeId); - return decryptWithNonce(iv); - }; - - if (spanState.type === SPANState.SPAN) { - // There SHOULD be a shared SPAN between both parties. But experience has shown that both could have - // sent a command at roughly the same time, using the same SPAN for encryption. - // To avoid a nasty desync and both nodes trying to resync at the same time, causing message loss, - // we accept commands encrypted with the previous SPAN under very specific circumstances: - if ( - // The previous SPAN is still known, i.e. the node didn't send another command that was successfully decrypted - !!spanState.currentSPAN - // it is still valid - && spanState.currentSPAN.expires > highResTimestamp() - // The received command is exactly the next, expected one - && prevSequenceNumber != undefined - && this["_sequenceNumber"] === ((prevSequenceNumber + 1) & 0xff) - // And in case of a mock-based test, do this only on the controller - && !this.host.__internalIsMockNode - ) { - const nonce = spanState.currentSPAN.nonce; - spanState.currentSPAN = undefined; - - // If we could decrypt this way, we're done... - const result = decryptWithNonce(nonce); - if (result.authOK) { - return { - ...result, - securityClass: spanState.securityClass, - }; - } - // ...otherwise, we need to try the normal way - } else { - // forgetting the current SPAN shouldn't be necessary but better be safe than sorry - spanState.currentSPAN = undefined; - } - - // This can only happen if the security class is known - return { - ...getNonceAndDecrypt(), - securityClass: spanState.securityClass, - }; - } else if (spanState.type === SPANState.LocalEI) { - // We've sent the other our receiver's EI and received its sender's EI, - // meaning we can now establish an SPAN - const senderEI = this.getSenderEI(); - if (!senderEI) failNoSPAN(); - const receiverEI = spanState.receiverEI; - - // How we do this depends on whether we know the security class of the other node - const isBootstrappingNode = this.securityManager.tempKeys.has( - sendingNodeId, - ); - if (isBootstrappingNode) { - // We're currently bootstrapping the node, it might be using a temporary key - this.securityManager.initializeTempSPAN( - sendingNodeId, - senderEI, - receiverEI, - ); - - const ret = getNonceAndDecrypt(); - // Decryption with the temporary key worked - if (ret.authOK) { - return { - ...ret, - securityClass: SecurityClass.Temporary, - }; - } - - // Reset the SPAN state and try with the recently granted security class - this.securityManager.setSPANState( - sendingNodeId, - spanState, - ); - } - - // When ending up here, one of two situations has occured: - // a) We've taken over an existing network and do not know the node's security class - // b) We know the security class, but we're about to establish a new SPAN. This may happen at a lower - // security class than the one the node normally uses, e.g. when we're being queried for securely - // supported CCs. - // In both cases, we should simply try decoding with multiple security classes, starting from the highest one. - // If this fails, we restore the previous (partial) SPAN state. - - // 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)!] - : securityClassOrder.filter( - (s) => - this.host.hasSecurityClass(sendingNodeId, s) - !== false, - ); - - for (const secClass of possibleSecurityClasses) { - // Skip security classes we don't have keys for - if ( - !this.securityManager.hasKeysForSecurityClass( - secClass, - ) - ) { - continue; - } - - // Initialize an SPAN with that security class - this.securityManager.initializeSPAN( - sendingNodeId, - secClass, - senderEI, - receiverEI, - ); - const ret = getNonceAndDecrypt(); - - // It worked, return the result - 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) - === undefined - ) { - this.host.setSecurityClass( - sendingNodeId, - secClass, - true, - ); - } - return { - ...ret, - securityClass: secClass, - }; - } else { - // Reset the SPAN state and try with the next security class - this.securityManager.setSPANState( - sendingNodeId, - spanState, - ); - } - } } - // Nothing worked, fail the decryption return { - plaintext: Buffer.from([]), - authOK: false, - securityClass: undefined, - }; - } - - private decryptMulticast( - sendingNodeId: number, - groupId: number, - ciphertext: Buffer, - authData: Buffer, - authTag: Buffer, - ): DecryptionResult { - const iv = this.securityManager.nextPeerMPAN( - sendingNodeId, - groupId, - ); - const { keyCCM: key } = this.securityManager.getKeysForNode( - sendingNodeId, - ); - return { - key, - iv, - ...decryptAES128CCM(key, iv, ciphertext, authData, authTag), - // The security class is irrelevant when decrypting multicast commands - securityClass: undefined, + ...super.toLogEntry(ctx), + message, }; } } // @publicAPI export type Security2CCNonceReportOptions = - | { - MOS: boolean; - SOS: true; - receiverEI: Buffer; - } - | { - MOS: true; - SOS: false; - receiverEI?: undefined; - }; + & { + sequenceNumber?: number; + } + & ( + | { + 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), + options: WithAddress, ) { - super(host, options); + super(options); + + this.SOS = options.SOS; + this.MOS = options.MOS; + this.sequenceNumber = options.sequenceNumber; + if (options.SOS) this.receiverEI = options.receiverEI; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCNonceReport { // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this._sequenceNumber = this.payload[0]; - // Don't accept duplicate commands - validateSequenceNumber.call(this, this._sequenceNumber); - - this.MOS = !!(this.payload[1] & 0b10); - this.SOS = !!(this.payload[1] & 0b1); - validatePayload(this.MOS || this.SOS); - - if (this.SOS) { - // If the SOS flag is set, the REI field MUST be included in the command - validatePayload(this.payload.length >= 18); - this.receiverEI = this.payload.subarray(2, 18); - - // In that case we also need to store it, so the next sent command - // can use it for encryption - this.securityManager.storeRemoteEI( - this.nodeId as number, - this.receiverEI, - ); - } + const securityManager = assertSecurityRX(ctx); + + validatePayload(raw.payload.length >= 2); + const sequenceNumber = raw.payload[0]; + + // Don't accept duplicate commands + validateSequenceNumber( + securityManager, + ctx.sourceNodeId, + sequenceNumber, + ); + const MOS = !!(raw.payload[1] & 0b10); + const SOS = !!(raw.payload[1] & 0b1); + + if (SOS) { + // If the SOS flag is set, the REI field MUST be included in the command + validatePayload(raw.payload.length >= 18); + const receiverEI = raw.payload.subarray(2, 18); + + // In that case we also need to store it, so the next sent command + // can use it for encryption + securityManager.storeRemoteEI( + ctx.sourceNodeId, + receiverEI, + ); + + return new Security2CCNonceReport({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + MOS, + SOS, + receiverEI, + }); + } else if (MOS) { + return new Security2CCNonceReport({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + MOS, + SOS: false, + }); } else { - this.SOS = options.SOS; - this.MOS = options.MOS; - if (options.SOS) this.receiverEI = options.receiverEI; + validatePayload.fail("Either MOS or SOS must be set"); } } - private securityManager: SecurityManager2; - private _sequenceNumber: number | undefined; - /** - * Return the sequence number of this command. - * - * **WARNING:** If the sequence number hasn't been set before, this will create a new one. - * When sending messages, this should only happen immediately before serializing. - */ - public get sequenceNumber(): number { - if (this._sequenceNumber == undefined) { - this._sequenceNumber = this.securityManager - .nextSequenceNumber( - this.nodeId as number, - ); + public sequenceNumber: number | undefined; + private ensureSequenceNumber( + securityManager: SecurityManager2, + ): asserts this is this & { + sequenceNumber: number; + } { + if (this.sequenceNumber == undefined) { + this.sequenceNumber = securityManager.nextSequenceNumber( + this.nodeId as number, + ); } - return this._sequenceNumber; } public readonly SOS: boolean; public readonly MOS: boolean; public readonly receiverEI?: Buffer; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { + const securityManager = assertSecurityTX(ctx, this.nodeId); + this.ensureSequenceNumber(securityManager); + this.payload = Buffer.from([ this.sequenceNumber, (this.MOS ? 0b10 : 0) + (this.SOS ? 0b1 : 0), @@ -1995,12 +2082,12 @@ 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, + "sequence number": this.sequenceNumber ?? "(not set)", SOS: this.SOS, MOS: this.MOS, }; @@ -2008,62 +2095,76 @@ export class Security2CCNonceReport extends Security2CC { message["receiver entropy"] = buffer2hex(this.receiverEI); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface Security2CCNonceGetOptions { + sequenceNumber?: number; +} + @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: WithAddress, + ) { + super(options); + this.sequenceNumber = options.sequenceNumber; + } - // Make sure that we can send/receive secure commands - assertSecurity.call(this, options); - this.securityManager = getSecurityManager(host, this.nodeId)!; - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this._sequenceNumber = this.payload[0]; - // Don't accept duplicate commands - validateSequenceNumber.call(this, this._sequenceNumber); - } else { - // No options here - } + public static from(raw: CCRaw, ctx: CCParsingContext): Security2CCNonceGet { + const securityManager = assertSecurityRX(ctx); + + validatePayload(raw.payload.length >= 1); + const sequenceNumber = raw.payload[0]; + + // Don't accept duplicate commands + validateSequenceNumber( + securityManager, + ctx.sourceNodeId, + sequenceNumber, + ); + + return new Security2CCNonceGet({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + }); } - private securityManager: SecurityManager2; - private _sequenceNumber: number | undefined; - /** - * Return the sequence number of this command. - * - * **WARNING:** If the sequence number hasn't been set before, this will create a new one. - * When sending messages, this should only happen immediately before serializing. - */ - public get sequenceNumber(): number { - if (this._sequenceNumber == undefined) { - this._sequenceNumber = this.securityManager - .nextSequenceNumber( - this.nodeId as number, - ); + public sequenceNumber: number | undefined; + private ensureSequenceNumber( + securityManager: SecurityManager2, + ): asserts this is this & { + sequenceNumber: number; + } { + if (this.sequenceNumber == undefined) { + this.sequenceNumber = securityManager.nextSequenceNumber( + this.nodeId as number, + ); } - return this._sequenceNumber; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { + const securityManager = assertSecurityTX(ctx, this.nodeId); + this.ensureSequenceNumber(securityManager); + 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), - message: { "sequence number": this.sequenceNumber }, + ...super.toLogEntry(ctx), + message: { + "sequence number": this.sequenceNumber ?? "(not set)", + }, }; } } @@ -2081,39 +2182,51 @@ export interface Security2CCKEXReportOptions { @CCCommand(Security2Command.KEXReport) export class Security2CCKEXReport extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & Security2CCKEXReportOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.requestCSA = !!(this.payload[0] & 0b10); - this.echo = !!(this.payload[0] & 0b1); - // Remember the reserved bits for the echo - this._reserved = this.payload[0] & 0b1111_1100; - // The bit mask starts at 0, but bit 0 is not used - this.supportedKEXSchemes = parseBitMask( - this.payload.subarray(1, 2), - 0, - ).filter((s) => s !== 0); - this.supportedECDHProfiles = parseBitMask( - this.payload.subarray(2, 3), - ECDHProfiles.Curve25519, - ); - this.requestedKeys = parseBitMask( - this.payload.subarray(3, 4), - SecurityClass.S2_Unauthenticated, - ); - } else { - this.requestCSA = options.requestCSA; - this.echo = options.echo; - this._reserved = options._reserved ?? 0; - this.supportedKEXSchemes = options.supportedKEXSchemes; - this.supportedECDHProfiles = options.supportedECDHProfiles; - this.requestedKeys = options.requestedKeys; - } + super(options); + this.requestCSA = options.requestCSA; + this.echo = options.echo; + this._reserved = options._reserved ?? 0; + this.supportedKEXSchemes = options.supportedKEXSchemes; + this.supportedECDHProfiles = options.supportedECDHProfiles; + this.requestedKeys = options.requestedKeys; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCKEXReport { + validatePayload(raw.payload.length >= 4); + const requestCSA = !!(raw.payload[0] & 0b10); + const echo = !!(raw.payload[0] & 0b1); + + // Remember the reserved bits for the echo + const _reserved = raw.payload[0] & 0b1111_1100; + + // The bit mask starts at 0, but bit 0 is not used + const supportedKEXSchemes: KEXSchemes[] = parseBitMask( + raw.payload.subarray(1, 2), + 0, + ).filter((s) => s !== 0); + const supportedECDHProfiles: ECDHProfiles[] = parseBitMask( + raw.payload.subarray(2, 3), + ECDHProfiles.Curve25519, + ); + const requestedKeys: SecurityClass[] = parseBitMask( + raw.payload.subarray(3, 4), + SecurityClass.S2_Unauthenticated, + ); + + return new Security2CCKEXReport({ + nodeId: ctx.sourceNodeId, + requestCSA, + echo, + _reserved, + supportedKEXSchemes, + supportedECDHProfiles, + requestedKeys, + }); } public readonly _reserved: number; @@ -2123,7 +2236,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 +2256,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,44 +2319,50 @@ function testExpectedResponseForKEXSet( @expectedCCResponse(getExpectedResponseForKEXSet, testExpectedResponseForKEXSet) export class Security2CCKEXSet extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & Security2CCKEXSetOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this._reserved = this.payload[0] & 0b1111_1100; - this.permitCSA = !!(this.payload[0] & 0b10); - this.echo = !!(this.payload[0] & 0b1); - // The bit mask starts at 0, but bit 0 is not used - const selectedKEXSchemes = parseBitMask( - this.payload.subarray(1, 2), - 0, - ).filter((s) => s !== 0); - validatePayload(selectedKEXSchemes.length === 1); - this.selectedKEXScheme = selectedKEXSchemes[0]; - - const selectedECDHProfiles = parseBitMask( - this.payload.subarray(2, 3), - ECDHProfiles.Curve25519, - ); - validatePayload(selectedECDHProfiles.length === 1); - this.selectedECDHProfile = selectedECDHProfiles[0]; + super(options); + this.permitCSA = options.permitCSA; + this.echo = options.echo; + this._reserved = options._reserved ?? 0; + this.selectedKEXScheme = options.selectedKEXScheme; + this.selectedECDHProfile = options.selectedECDHProfile; + this.grantedKeys = options.grantedKeys; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): Security2CCKEXSet { + validatePayload(raw.payload.length >= 4); + const _reserved = raw.payload[0] & 0b1111_1100; + const permitCSA = !!(raw.payload[0] & 0b10); + const echo = !!(raw.payload[0] & 0b1); + + // The bit mask starts at 0, but bit 0 is not used + const selectedKEXSchemes = parseBitMask( + raw.payload.subarray(1, 2), + 0, + ).filter((s) => s !== 0); + validatePayload(selectedKEXSchemes.length === 1); + const selectedKEXScheme: KEXSchemes = selectedKEXSchemes[0]; + const selectedECDHProfiles = parseBitMask( + raw.payload.subarray(2, 3), + ECDHProfiles.Curve25519, + ); + validatePayload(selectedECDHProfiles.length === 1); + const selectedECDHProfile: ECDHProfiles = selectedECDHProfiles[0]; + const grantedKeys: SecurityClass[] = parseBitMask( + raw.payload.subarray(3, 4), + SecurityClass.S2_Unauthenticated, + ); - this.grantedKeys = parseBitMask( - this.payload.subarray(3, 4), - SecurityClass.S2_Unauthenticated, - ); - } else { - this.permitCSA = options.permitCSA; - this.echo = options.echo; - this._reserved = options._reserved ?? 0; - this.selectedKEXScheme = options.selectedKEXScheme; - this.selectedECDHProfile = options.selectedECDHProfile; - this.grantedKeys = options.grantedKeys; - } + return new Security2CCKEXSet({ + nodeId: ctx.sourceNodeId, + _reserved, + permitCSA, + echo, + selectedKEXScheme, + selectedECDHProfile, + grantedKeys, + }); } public readonly _reserved: number; @@ -2253,7 +2372,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 +2392,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( @@ -2299,42 +2418,46 @@ export class Security2CCKEXSet extends Security2CC { } // @publicAPI -export interface Security2CCKEXFailOptions extends CCCommandOptions { +export interface Security2CCKEXFailOptions { failType: KEXFailType; } @CCCommand(Security2Command.KEXFail) export class Security2CCKEXFail extends Security2CC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | Security2CCKEXFailOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.failType = this.payload[0]; - } else { - this.failType = options.failType; - } + super(options); + this.failType = options.failType; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): Security2CCKEXFail { + validatePayload(raw.payload.length >= 1); + const failType: KEXFailType = raw.payload[0]; + + return new Security2CCKEXFail({ + nodeId: ctx.sourceNodeId, + failType, + }); } 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) }, }; } } // @publicAPI -export interface Security2CCPublicKeyReportOptions extends CCCommandOptions { +export interface Security2CCPublicKeyReportOptions { includingNode: boolean; publicKey: Buffer; } @@ -2342,36 +2465,42 @@ export interface Security2CCPublicKeyReportOptions extends CCCommandOptions { @CCCommand(Security2Command.PublicKeyReport) export class Security2CCPublicKeyReport extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCPublicKeyReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 17); - this.includingNode = !!(this.payload[0] & 0b1); - this.publicKey = this.payload.subarray(1); - } else { - this.includingNode = options.includingNode; - this.publicKey = options.publicKey; - } + super(options); + this.includingNode = options.includingNode; + this.publicKey = options.publicKey; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCPublicKeyReport { + validatePayload(raw.payload.length >= 17); + const includingNode = !!(raw.payload[0] & 0b1); + const publicKey: Buffer = raw.payload.subarray(1); + + return new Security2CCPublicKeyReport({ + nodeId: ctx.sourceNodeId, + includingNode, + publicKey, + }); } 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), @@ -2381,7 +2510,7 @@ export class Security2CCPublicKeyReport extends Security2CC { } // @publicAPI -export interface Security2CCNetworkKeyReportOptions extends CCCommandOptions { +export interface Security2CCNetworkKeyReportOptions { grantedKey: SecurityClass; networkKey: Buffer; } @@ -2389,36 +2518,45 @@ export interface Security2CCNetworkKeyReportOptions extends CCCommandOptions { @CCCommand(Security2Command.NetworkKeyReport) export class Security2CCNetworkKeyReport extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCNetworkKeyReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 17); - this.grantedKey = bitMaskToSecurityClass(this.payload, 0); - this.networkKey = this.payload.subarray(1, 17); - } else { - this.grantedKey = options.grantedKey; - this.networkKey = options.networkKey; - } + super(options); + this.grantedKey = options.grantedKey; + this.networkKey = options.networkKey; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCNetworkKeyReport { + validatePayload(raw.payload.length >= 17); + const grantedKey: SecurityClass = bitMaskToSecurityClass( + raw.payload, + 0, + ); + const networkKey = raw.payload.subarray(1, 17); + + return new Security2CCNetworkKeyReport({ + nodeId: ctx.sourceNodeId, + grantedKey, + networkKey, + }); } 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, @@ -2432,7 +2570,7 @@ export class Security2CCNetworkKeyReport extends Security2CC { } // @publicAPI -export interface Security2CCNetworkKeyGetOptions extends CCCommandOptions { +export interface Security2CCNetworkKeyGetOptions { requestedKey: SecurityClass; } @@ -2441,30 +2579,38 @@ export interface Security2CCNetworkKeyGetOptions extends CCCommandOptions { // FIXME: maybe use the dynamic @expectedCCResponse instead? export class Security2CCNetworkKeyGet extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCNetworkKeyGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.requestedKey = bitMaskToSecurityClass(this.payload, 0); - } else { - this.requestedKey = options.requestedKey; - } + super(options); + this.requestedKey = options.requestedKey; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCNetworkKeyGet { + validatePayload(raw.payload.length >= 1); + const requestedKey: SecurityClass = bitMaskToSecurityClass( + raw.payload, + 0, + ); + + return new Security2CCNetworkKeyGet({ + nodeId: ctx.sourceNodeId, + requestedKey, + }); } 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, @@ -2479,7 +2625,7 @@ export class Security2CCNetworkKeyGet extends Security2CC { export class Security2CCNetworkKeyVerify extends Security2CC {} // @publicAPI -export interface Security2CCTransferEndOptions extends CCCommandOptions { +export interface Security2CCTransferEndOptions { keyVerified: boolean; keyRequestComplete: boolean; } @@ -2487,35 +2633,41 @@ export interface Security2CCTransferEndOptions extends CCCommandOptions { @CCCommand(Security2Command.TransferEnd) export class Security2CCTransferEnd extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCTransferEndOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.keyVerified = !!(this.payload[0] & 0b10); - this.keyRequestComplete = !!(this.payload[0] & 0b1); - } else { - this.keyVerified = options.keyVerified; - this.keyRequestComplete = options.keyRequestComplete; - } + super(options); + this.keyVerified = options.keyVerified; + this.keyRequestComplete = options.keyRequestComplete; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCTransferEnd { + validatePayload(raw.payload.length >= 1); + const keyVerified = !!(raw.payload[0] & 0b10); + const keyRequestComplete = !!(raw.payload[0] & 0b1); + + return new Security2CCTransferEnd({ + nodeId: ctx.sourceNodeId, + keyVerified, + keyRequestComplete, + }); } 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, @@ -2525,43 +2677,46 @@ export class Security2CCTransferEnd extends Security2CC { } // @publicAPI -export interface Security2CCCommandsSupportedReportOptions - extends CCCommandOptions -{ +export interface Security2CCCommandsSupportedReportOptions { supportedCCs: CommandClasses[]; } @CCCommand(Security2Command.CommandsSupportedReport) export class Security2CCCommandsSupportedReport extends Security2CC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | Security2CCCommandsSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - const CCs = parseCCList(this.payload); - // SDS13783: A sending node MAY terminate the list of supported command classes with the - // COMMAND_CLASS_MARK command class identifier. - // A receiving node MUST stop parsing the list of supported command classes if it detects the - // COMMAND_CLASS_MARK command class identifier in the Security 2 Commands Supported Report - this.supportedCCs = CCs.supportedCCs; - } else { - this.supportedCCs = options.supportedCCs; - } + super(options); + this.supportedCCs = options.supportedCCs; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): Security2CCCommandsSupportedReport { + const CCs = parseCCList(raw.payload); + // SDS13783: A sending node MAY terminate the list of supported command classes with the + // COMMAND_CLASS_MARK command class identifier. + // A receiving node MUST stop parsing the list of supported command classes if it detects the + // COMMAND_CLASS_MARK command class identifier in the Security 2 Commands Supported Report + const supportedCCs = CCs.supportedCCs; + + return new Security2CCCommandsSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedCCs, + }); } 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..22063a81c72c 100644 --- a/packages/cc/src/cc/SecurityCC.ts +++ b/packages/cc/src/cc/SecurityCC.ts @@ -7,6 +7,7 @@ import { SecurityClass, type SecurityManager, TransmitOptions, + type WithAddress, ZWaveError, ZWaveErrorCodes, computeMAC, @@ -22,19 +23,18 @@ 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"; import { randomBytes } from "node:crypto"; import { CCAPI, PhysicalCCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -79,6 +79,42 @@ function throwNoNonce(reason?: string): never { const HALF_NONCE_SIZE = 8; +function assertSecurityRX( + ctx: CCParsingContext, +): asserts ctx is CCParsingContext & { securityManager: SecurityManager } { + if (!ctx.ownNodeId) { + throw new ZWaveError( + `Secure commands (S0) can only be decoded when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + if (!ctx.securityManager) { + throw new ZWaveError( + `Secure commands (S0) can only be decoded when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } +} + +function assertSecurityTX( + ctx: CCEncodingContext, +): asserts ctx is CCEncodingContext & { securityManager: SecurityManager } { + if (!ctx.ownNodeId) { + throw new ZWaveError( + `Secure commands (S0) can only be sent when the controller's node id is known!`, + ZWaveErrorCodes.Driver_NotReady, + ); + } + + if (!ctx.securityManager) { + throw new ZWaveError( + `Secure commands (S0) can only be sent when the security manager is set up!`, + ZWaveErrorCodes.Driver_NoSecurity, + ); + } +} + // TODO: Ignore commands if received via multicast // Encapsulation CCs are used internally and too frequently that we @@ -112,11 +148,11 @@ export class SecurityCCAPI extends PhysicalCCAPI { requestNextNonce ? SecurityCCCommandEncapsulationNonceGet : SecurityCCCommandEncapsulation - )(this.applHost, { + )({ nodeId: this.endpoint.nodeId, encapsulated, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } /** @@ -125,11 +161,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, { ...this.commandOptions, @@ -140,13 +176,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 +198,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, + endpointIndex: 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 +233,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 +246,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, + endpointIndex: 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 +261,18 @@ 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, + endpointIndex: this.endpoint.index, }); if (encapsulated) { - cc = new SecurityCCCommandEncapsulation(this.applHost, { + cc = new SecurityCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, encapsulated: cc, }); } - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } public async inheritSecurityScheme(): Promise { @@ -245,11 +281,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, + endpointIndex: 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 +295,18 @@ 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, + endpointIndex: this.endpoint.index, networkKey, }); - const cc = new SecurityCCCommandEncapsulation(this.applHost, { + const cc = new SecurityCCCommandEncapsulation({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, 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 +315,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, + endpointIndex: 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 +329,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SecurityCCCommandsSupportedReport >( cc, @@ -317,13 +353,14 @@ 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, + endpointIndex: this.endpoint.index, supportedCCs, controlledCCs, + reportsToFollow: 0, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -333,23 +370,21 @@ 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)!; + + 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 +401,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 +413,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 +422,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 +442,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 +478,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,11 +529,12 @@ 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, encapsulated: cc, }); @@ -512,45 +548,49 @@ export class SecurityCC extends CommandClass { } } -interface SecurityCCNonceReportOptions extends CCCommandOptions { +interface SecurityCCNonceReportOptions { nonce: Buffer; } @CCCommand(SecurityCommand.NonceReport) export class SecurityCCNonceReport extends SecurityCC { constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SecurityCCNonceReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload.withReason("Invalid nonce length")( - this.payload.length === HALF_NONCE_SIZE, + super(options); + if (options.nonce.length !== HALF_NONCE_SIZE) { + throw new ZWaveError( + `Nonce must have length ${HALF_NONCE_SIZE}!`, + ZWaveErrorCodes.Argument_Invalid, ); - this.nonce = this.payload; - } else { - if (options.nonce.length !== HALF_NONCE_SIZE) { - throw new ZWaveError( - `Nonce must have length ${HALF_NONCE_SIZE}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.nonce = options.nonce; } + this.nonce = options.nonce; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SecurityCCNonceReport { + validatePayload.withReason("Invalid nonce length")( + raw.payload.length === HALF_NONCE_SIZE, + ); + + return new SecurityCCNonceReport({ + nodeId: ctx.sourceNodeId, + nonce: raw.payload, + }); } 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) }, }; } @@ -561,12 +601,18 @@ export class SecurityCCNonceReport extends SecurityCC { export class SecurityCCNonceGet extends SecurityCC {} // @publicAPI -export interface SecurityCCCommandEncapsulationOptions - extends CCCommandOptions -{ - encapsulated: CommandClass; - alternativeNetworkKey?: Buffer; -} +export type SecurityCCCommandEncapsulationOptions = + & { + alternativeNetworkKey?: Buffer; + } + & ({ + encapsulated: CommandClass; + } | { + decryptedCCBytes: Buffer; + sequenced: boolean; + secondFrame: boolean; + sequenceCounter: number; + }); function getCCResponseForCommandEncapsulation( sent: SecurityCCCommandEncapsulation, @@ -583,97 +629,96 @@ function getCCResponseForCommandEncapsulation( ) export class SecurityCCCommandEncapsulation extends SecurityCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SecurityCCCommandEncapsulationOptions, + options: WithAddress, ) { - 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, - ); + if ("encapsulated" in options) { + this.encapsulated = options.encapsulated; + this.encapsulated.encapsulatingCC = this as any; + } else { + this.decryptedCCBytes = options.decryptedCCBytes; + this.sequenced = options.sequenced; + this.secondFrame = options.secondFrame; + this.sequenceCounter = options.sequenceCounter; } + this.alternativeNetworkKey = options.alternativeNetworkKey; + } - 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 - validatePayload( - this.payload.length >= HALF_NONCE_SIZE + 1 + 1 + 1 + 8, - ); - const iv = this.payload.subarray(0, HALF_NONCE_SIZE); - const encryptedPayload = this.payload.subarray(HALF_NONCE_SIZE, -9); - const nonceId = this.payload.at(-9)!; - const authCode = this.payload.subarray(-8); - - // Retrieve the used nonce from the nonce store - const nonce = this.host.securityManager.getNonce(nonceId); - // Only accept the message if the nonce hasn't expired - validatePayload.withReason( + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SecurityCCCommandEncapsulation { + assertSecurityRX(ctx); + + // HALF_NONCE_SIZE bytes iv, 1 byte frame control, at least 1 CC byte, 1 byte nonce id, 8 bytes auth code + validatePayload( + raw.payload.length >= HALF_NONCE_SIZE + 1 + 1 + 1 + 8, + ); + const iv = raw.payload.subarray(0, HALF_NONCE_SIZE); + const encryptedPayload = raw.payload.subarray(HALF_NONCE_SIZE, -9); + const nonceId = raw.payload.at(-9)!; + const authCode = raw.payload.subarray(-8); + + // Retrieve the used nonce from the nonce store + const nonce = ctx.securityManager.getNonce(nonceId); + // Only accept the message if the nonce hasn't expired + if (!nonce) { + validatePayload.fail( `Nonce ${ num2hex( nonceId, ) } expired, cannot decode security encapsulated command.`, - )(!!nonce); - // and mark the nonce as used - this.host.securityManager.deleteNonce(nonceId); - - this.authKey = this.host.securityManager.authKey; - this.encryptionKey = this.host.securityManager.encryptionKey; - - // Validate the encrypted data - const authData = getAuthenticationData( - iv, - nonce!, - this.ccCommand, - this.nodeId, - this.host.ownNodeId, - encryptedPayload, ); - const expectedAuthCode = computeMAC(authData, this.authKey); - // Only accept messages with a correct auth code - validatePayload.withReason( - "Invalid auth code, won't accept security encapsulated command.", - )(authCode.equals(expectedAuthCode)); - - // Decrypt the encapsulated CC - const frameControlAndDecryptedCC = decryptAES128OFB( - encryptedPayload, - this.encryptionKey, - Buffer.concat([iv, nonce!]), - ); - const frameControl = frameControlAndDecryptedCC[0]; - this.sequenceCounter = frameControl & 0b1111; - this.sequenced = !!(frameControl & 0b1_0000); - this.secondFrame = !!(frameControl & 0b10_0000); + } + // and mark the nonce as used + ctx.securityManager.deleteNonce(nonceId); - this.decryptedCCBytes = frameControlAndDecryptedCC.subarray(1); + // Validate the encrypted data + const authData = getAuthenticationData( + iv, + nonce, + SecurityCommand.CommandEncapsulation, + ctx.sourceNodeId, + ctx.ownNodeId, + encryptedPayload, + ); + const expectedAuthCode = computeMAC( + authData, + ctx.securityManager.authKey, + ); + // Only accept messages with a correct auth code + validatePayload.withReason( + "Invalid auth code, won't accept security encapsulated command.", + )(authCode.equals(expectedAuthCode)); + + // Decrypt the encapsulated CC + const frameControlAndDecryptedCC = decryptAES128OFB( + encryptedPayload, + ctx.securityManager.encryptionKey, + Buffer.concat([iv, nonce]), + ); + const frameControl = frameControlAndDecryptedCC[0]; + const sequenceCounter = frameControl & 0b1111; + const sequenced = !!(frameControl & 0b1_0000); + const secondFrame = !!(frameControl & 0b10_0000); + const decryptedCCBytes: Buffer | undefined = frameControlAndDecryptedCC + .subarray(1); + + const ret = new SecurityCCCommandEncapsulation({ + nodeId: ctx.sourceNodeId, + sequenceCounter, + sequenced, + secondFrame, + decryptedCCBytes, + }); - // Remember for debugging purposes - this.authData = authData; - this.authCode = authCode; - this.iv = iv; - } else { - this.encapsulated = options.encapsulated; - options.encapsulated.encapsulatingCC = this as any; - if (options.alternativeNetworkKey) { - this.authKey = generateAuthKey(options.alternativeNetworkKey); - this.encryptionKey = generateEncryptionKey( - options.alternativeNetworkKey, - ); - } else { - this.authKey = this.host.securityManager.authKey; - this.encryptionKey = this.host.securityManager.encryptionKey; - } - } + ret.authData = authData; + ret.authCode = authCode; + ret.iv = iv; + + return ret; } private sequenced: boolean | undefined; @@ -683,12 +728,10 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { private decryptedCCBytes: Buffer | undefined; public encapsulated!: CommandClass; - private authKey: Buffer; - private encryptionKey: Buffer; + private alternativeNetworkKey?: Buffer; public get nonceId(): number | undefined { - if (!this.nonce) return undefined; - return this.host.securityManager.getNonceId(this.nonce); + return this.nonce?.[0]; } public nonce: Buffer | undefined; @@ -718,8 +761,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 +771,30 @@ 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, { - data: this.decryptedCCBytes, - fromEncapsulation: true, - encapCC: this, - }); + this.encapsulated = CommandClass.parse(this.decryptedCCBytes, ctx); + this.encapsulated.encapsulatingCC = this as any; } - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { if (!this.nonce) throwNoNonce(); if (this.nonce.length !== HALF_NONCE_SIZE) { throwNoNonce("Invalid nonce size"); } + assertSecurityTX(ctx); + + let authKey: Buffer; + let encryptionKey: Buffer; + if (this.alternativeNetworkKey) { + authKey = generateAuthKey(this.alternativeNetworkKey); + encryptionKey = generateEncryptionKey( + this.alternativeNetworkKey, + ); + } else { + authKey = ctx.securityManager.authKey; + encryptionKey = ctx.securityManager.encryptionKey; + } - const serializedCC = this.encapsulated.serialize(); + const serializedCC = this.encapsulated.serialize(ctx); const plaintext = Buffer.concat([ Buffer.from([0]), // TODO: frame control serializedCC, @@ -749,17 +802,17 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { // Encrypt the payload const senderNonce = randomBytes(HALF_NONCE_SIZE); const iv = Buffer.concat([senderNonce, this.nonce]); - const ciphertext = encryptAES128OFB(plaintext, this.encryptionKey, iv); + const ciphertext = encryptAES128OFB(plaintext, encryptionKey, iv); // And generate the auth code const authData = getAuthenticationData( senderNonce, this.nonce, this.ccCommand, - this.host.ownNodeId, + ctx.ownNodeId, this.nodeId, ciphertext, ); - const authCode = computeMAC(authData, this.authKey); + const authCode = computeMAC(authData, authKey); // Remember for debugging purposes this.iv = iv; @@ -773,7 +826,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { Buffer.from([this.nonceId!]), authCode, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -781,7 +834,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 +871,7 @@ export class SecurityCCCommandEncapsulation extends SecurityCC { } } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -832,26 +885,26 @@ export class SecurityCCCommandEncapsulationNonceGet @CCCommand(SecurityCommand.SchemeReport) export class SecurityCCSchemeReport extends SecurityCC { - public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - // The including controller MUST NOT perform any validation of the Supported Security Schemes byte - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SecurityCCSchemeReport { + validatePayload(raw.payload.length >= 1); + // The including controller MUST NOT perform any validation of the Supported Security Schemes byte + return new SecurityCCSchemeReport({ + nodeId: ctx.sourceNodeId, + }); } - 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, }; @@ -861,23 +914,15 @@ export class SecurityCCSchemeReport extends SecurityCC { @CCCommand(SecurityCommand.SchemeGet) @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeGet extends SecurityCC { - public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, - ) { - super(host, 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, }; @@ -887,23 +932,15 @@ export class SecurityCCSchemeGet extends SecurityCC { @CCCommand(SecurityCommand.SchemeInherit) @expectedCCResponse(SecurityCCSchemeReport) export class SecurityCCSchemeInherit extends SecurityCC { - public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, - ) { - super(host, 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, }; @@ -914,7 +951,7 @@ export class SecurityCCSchemeInherit extends SecurityCC { export class SecurityCCNetworkKeyVerify extends SecurityCC {} // @publicAPI -export interface SecurityCCNetworkKeySetOptions extends CCCommandOptions { +export interface SecurityCCNetworkKeySetOptions { networkKey: Buffer; } @@ -922,44 +959,48 @@ export interface SecurityCCNetworkKeySetOptions extends CCCommandOptions { @expectedCCResponse(SecurityCCNetworkKeyVerify) export class SecurityCCNetworkKeySet extends SecurityCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SecurityCCNetworkKeySetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 16); - this.networkKey = this.payload.subarray(0, 16); - } else { - if (options.networkKey.length !== 16) { - throw new ZWaveError( - `The network key must have length 16!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.networkKey = options.networkKey; + super(options); + if (options.networkKey.length !== 16) { + throw new ZWaveError( + `The network key must have length 16!`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.networkKey = options.networkKey; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SecurityCCNetworkKeySet { + validatePayload(raw.payload.length >= 16); + const networkKey: Buffer = raw.payload.subarray(0, 16); + + return new SecurityCCNetworkKeySet({ + nodeId: ctx.sourceNodeId, + networkKey, + }); } 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; } } // @publicAPI -export interface SecurityCCCommandsSupportedReportOptions - extends CCCommandOptions -{ +export interface SecurityCCCommandsSupportedReportOptions { + reportsToFollow?: number; supportedCCs: CommandClasses[]; controlledCCs: CommandClasses[]; } @@ -967,37 +1008,38 @@ export interface SecurityCCCommandsSupportedReportOptions @CCCommand(SecurityCommand.CommandsSupportedReport) export class SecurityCCCommandsSupportedReport extends SecurityCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SecurityCCCommandsSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.reportsToFollow = this.payload[0]; - const list = parseCCList(this.payload.subarray(1)); - this.supportedCCs = list.supportedCCs; - this.controlledCCs = list.controlledCCs; - } else { - this.supportedCCs = options.supportedCCs; - this.controlledCCs = options.controlledCCs; - // TODO: properly split the CCs into multiple reports - this.reportsToFollow = 0; - } - } + super(options); - public readonly reportsToFollow: number; + this.supportedCCs = options.supportedCCs; + this.controlledCCs = options.controlledCCs; + // TODO: properly split the CCs into multiple reports + this.reportsToFollow = options.reportsToFollow ?? 0; + } - public serialize(): Buffer { - this.payload = Buffer.concat([ - Buffer.from([this.reportsToFollow]), - encodeCCList(this.supportedCCs, this.controlledCCs), - ]); - return super.serialize(); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SecurityCCCommandsSupportedReport { + validatePayload(raw.payload.length >= 1); + const reportsToFollow = raw.payload[0]; + const list = parseCCList(raw.payload.subarray(1)); + const supportedCCs: CommandClasses[] = list.supportedCCs; + const controlledCCs: CommandClasses[] = list.controlledCCs; + + return new SecurityCCCommandsSupportedReport({ + nodeId: ctx.sourceNodeId, + reportsToFollow, + supportedCCs, + controlledCCs, + }); } + public reportsToFollow: number; + public supportedCCs: CommandClasses[]; + public controlledCCs: CommandClasses[]; + public getPartialCCSessionId(): Record | undefined { // Nothing special we can distinguish sessions with return {}; @@ -1007,11 +1049,7 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { return this.reportsToFollow > 0; } - public supportedCCs: CommandClasses[]; - public controlledCCs: CommandClasses[]; - public mergePartialCCs( - applHost: ZWaveApplicationHost, partials: SecurityCCCommandsSupportedReport[], ): void { // Concat the lists of CCs @@ -1023,9 +1061,17 @@ export class SecurityCCCommandsSupportedReport extends SecurityCC { .reduce((prev, cur) => prev.concat(...cur), []); } - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public serialize(ctx: CCEncodingContext): Buffer { + this.payload = Buffer.concat([ + Buffer.from([this.reportsToFollow]), + encodeCCList(this.supportedCCs, this.controlledCCs), + ]); + return super.serialize(ctx); + } + + 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..f382abe19903 100644 --- a/packages/cc/src/cc/SoundSwitchCC.ts +++ b/packages/cc/src/cc/SoundSwitchCC.ts @@ -6,15 +6,16 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, supervisedCommandSucceeded, 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"; @@ -29,11 +30,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, type CCResponsePredicate, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -119,11 +119,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCTonesNumberReport >( cc, @@ -140,12 +140,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, + endpointIndex: this.endpoint.index, toneId, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCToneInfoReport >( cc, @@ -164,13 +164,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, + endpointIndex: 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 +180,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCConfigurationReport >( cc, @@ -212,13 +212,13 @@ export class SoundSwitchCCAPI extends CCAPI { ); } - const cc = new SoundSwitchCCTonePlaySet(this.applHost, { + const cc = new SoundSwitchCCTonePlaySet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: 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 +227,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, + endpointIndex: 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 +243,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< SoundSwitchCCTonePlayReport >( cc, @@ -372,36 +372,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 +411,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 +420,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 +428,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 +437,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 +445,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 +453,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,44 +465,47 @@ duration: ${info.duration} seconds`; }); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } // @publicAPI -export interface SoundSwitchCCTonesNumberReportOptions - extends CCCommandOptions -{ +export interface SoundSwitchCCTonesNumberReportOptions { toneCount: number; } @CCCommand(SoundSwitchCommand.TonesNumberReport) export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SoundSwitchCCTonesNumberReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.toneCount = this.payload[0]; - } else { - this.toneCount = options.toneCount; - } + super(options); + this.toneCount = options.toneCount; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCTonesNumberReport { + validatePayload(raw.payload.length >= 1); + const toneCount = raw.payload[0]; + + return new SoundSwitchCCTonesNumberReport({ + nodeId: ctx.sourceNodeId, + toneCount, + }); } 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 }, }; } @@ -511,7 +516,7 @@ export class SoundSwitchCCTonesNumberReport extends SoundSwitchCC { export class SoundSwitchCCTonesNumberGet extends SoundSwitchCC {} // @publicAPI -export interface SoundSwitchCCToneInfoReportOptions extends CCCommandOptions { +export interface SoundSwitchCCToneInfoReportOptions { toneId: number; duration: number; name: string; @@ -520,44 +525,52 @@ export interface SoundSwitchCCToneInfoReportOptions extends CCCommandOptions { @CCCommand(SoundSwitchCommand.ToneInfoReport) export class SoundSwitchCCToneInfoReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SoundSwitchCCToneInfoReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.toneId = this.payload[0]; - this.duration = this.payload.readUInt16BE(1); - const nameLength = this.payload[3]; - validatePayload(this.payload.length >= 4 + nameLength); - this.name = this.payload.subarray(4, 4 + nameLength).toString( - "utf8", - ); - } else { - this.toneId = options.toneId; - this.duration = options.duration; - this.name = options.name; - } + super(options); + this.toneId = options.toneId; + this.duration = options.duration; + this.name = options.name; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCToneInfoReport { + validatePayload(raw.payload.length >= 4); + const toneId = raw.payload[0]; + const duration = raw.payload.readUInt16BE(1); + const nameLength = raw.payload[3]; + + validatePayload(raw.payload.length >= 4 + nameLength); + const name = raw.payload.subarray(4, 4 + nameLength).toString( + "utf8", + ); + + return new SoundSwitchCCToneInfoReport({ + nodeId: ctx.sourceNodeId, + toneId, + duration, + name, + }); } public readonly toneId: number; 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`, @@ -575,7 +588,7 @@ const testResponseForSoundSwitchToneInfoGet: CCResponsePredicate< }; // @publicAPI -export interface SoundSwitchCCToneInfoGetOptions extends CCCommandOptions { +export interface SoundSwitchCCToneInfoGetOptions { toneId: number; } @@ -586,37 +599,42 @@ export interface SoundSwitchCCToneInfoGetOptions extends CCCommandOptions { ) export class SoundSwitchCCToneInfoGet extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SoundSwitchCCToneInfoGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.toneId = this.payload[0]; - } else { - this.toneId = options.toneId; - } + super(options); + this.toneId = options.toneId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCToneInfoGet { + validatePayload(raw.payload.length >= 1); + const toneId = raw.payload[0]; + + return new SoundSwitchCCToneInfoGet({ + nodeId: ctx.sourceNodeId, + toneId, + }); } 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 }, }; } } // @publicAPI -export interface SoundSwitchCCConfigurationSetOptions extends CCCommandOptions { +export interface SoundSwitchCCConfigurationSetOptions { defaultVolume: number; defaultToneId: number; } @@ -625,33 +643,39 @@ export interface SoundSwitchCCConfigurationSetOptions extends CCCommandOptions { @useSupervision() export class SoundSwitchCCConfigurationSet extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | SoundSwitchCCConfigurationSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.defaultVolume = this.payload[0]; - this.defaultToneId = this.payload[1]; - } else { - this.defaultVolume = options.defaultVolume; - this.defaultToneId = options.defaultToneId; - } + super(options); + this.defaultVolume = options.defaultVolume; + this.defaultToneId = options.defaultToneId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCConfigurationSet { + validatePayload(raw.payload.length >= 2); + const defaultVolume = raw.payload[0]; + const defaultToneId = raw.payload[1]; + + return new SoundSwitchCCConfigurationSet({ + nodeId: ctx.sourceNodeId, + defaultVolume, + defaultToneId, + }); } 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,20 +693,26 @@ export interface SoundSwitchCCConfigurationReportOptions { @CCCommand(SoundSwitchCommand.ConfigurationReport) export class SoundSwitchCCConfigurationReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & SoundSwitchCCConfigurationReportOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.defaultVolume = clamp(this.payload[0], 0, 100); - this.defaultToneId = this.payload[1]; - } else { - this.defaultVolume = options.defaultVolume; - this.defaultToneId = options.defaultToneId; - } + super(options); + this.defaultVolume = options.defaultVolume; + this.defaultToneId = options.defaultToneId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCConfigurationReport { + validatePayload(raw.payload.length >= 2); + const defaultVolume = clamp(raw.payload[0], 0, 100); + const defaultToneId = raw.payload[1]; + + return new SoundSwitchCCConfigurationReport({ + nodeId: ctx.sourceNodeId, + defaultVolume, + defaultToneId, + }); } @ccValue(SoundSwitchCCValues.defaultVolume) @@ -691,14 +721,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,33 +752,40 @@ export interface SoundSwitchCCTonePlaySetOptions { @useSupervision() export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & SoundSwitchCCTonePlaySetOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.toneId = this.payload[0]; - if (this.toneId !== 0 && this.payload.length >= 2) { - this.volume = this.payload[1]; - } - } else { - this.toneId = options.toneId; - this.volume = options.volume; + super(options); + this.toneId = options.toneId; + this.volume = options.volume; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCTonePlaySet { + validatePayload(raw.payload.length >= 1); + const toneId = raw.payload[0]; + let volume: number | undefined; + if (toneId !== 0 && raw.payload.length >= 2) { + volume = raw.payload[1]; } + + return new SoundSwitchCCTonePlaySet({ + nodeId: ctx.sourceNodeId, + toneId, + volume, + }); } 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 +793,7 @@ export class SoundSwitchCCTonePlaySet extends SoundSwitchCC { message.volume = this.volume === 0 ? "default" : `${this.volume} %`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -772,22 +809,30 @@ export interface SoundSwitchCCTonePlayReportOptions { @CCCommand(SoundSwitchCommand.TonePlayReport) export class SoundSwitchCCTonePlayReport extends SoundSwitchCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & SoundSwitchCCTonePlayReportOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.toneId = this.payload[0]; - if (this.toneId !== 0 && this.payload.length >= 2) { - this.volume = this.payload[1]; - } - } else { - this.toneId = options.toneId; - this.volume = options.volume; + super(options); + this.toneId = options.toneId; + this.volume = options.volume; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): SoundSwitchCCTonePlayReport { + validatePayload(raw.payload.length >= 1); + const toneId = raw.payload[0]; + + let volume: number | undefined; + if (toneId !== 0 && raw.payload.length >= 2) { + volume = raw.payload[1]; } + + return new SoundSwitchCCTonePlayReport({ + nodeId: ctx.sourceNodeId, + toneId, + volume, + }); } @ccValue(SoundSwitchCCValues.toneId) @@ -796,12 +841,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 +854,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..b9c1343c95a3 100644 --- a/packages/cc/src/cc/SupervisionCC.ts +++ b/packages/cc/src/cc/SupervisionCC.ts @@ -2,33 +2,33 @@ 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, + type WithAddress, ZWaveError, ZWaveErrorCodes, isTransmissionError, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetNode, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { PhysicalCCAPI } from "../lib/API"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { API, CCCommand, @@ -88,9 +88,9 @@ 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, + endpointIndex: this.endpoint.index, ...cmdOptions, }); @@ -98,7 +98,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 +140,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 +151,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, + endpointIndex: cc.endpointIndex, encapsulated: cc, + sessionId, requestStatusUpdates, }); @@ -195,13 +196,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 +216,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 +231,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 +244,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 +257,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,30 +288,47 @@ export type SupervisionCCReportOptions = @CCCommand(SupervisionCommand.Report) export class SupervisionCCReport extends SupervisionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & SupervisionCCReportOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.moreUpdatesFollow = !!(this.payload[0] & 0b1_0_000000); - this.requestWakeUpOnDemand = !!(this.payload[0] & 0b0_1_000000); - this.sessionId = this.payload[0] & 0b111111; - this.status = this.payload[1]; - this.duration = Duration.parseReport(this.payload[2]); + super(options); + + this.moreUpdatesFollow = options.moreUpdatesFollow; + this.requestWakeUpOnDemand = !!options.requestWakeUpOnDemand; + this.sessionId = options.sessionId; + this.status = options.status; + if (options.status === SupervisionStatus.Working) { + this.duration = options.duration; } else { - this.moreUpdatesFollow = options.moreUpdatesFollow; - this.requestWakeUpOnDemand = !!options.requestWakeUpOnDemand; - this.sessionId = options.sessionId; - this.status = options.status; - if (options.status === SupervisionStatus.Working) { - this.duration = options.duration; - } else { - this.duration = new Duration(0, "seconds"); - } + this.duration = new Duration(0, "seconds"); + } + } + + public static from(raw: CCRaw, ctx: CCParsingContext): SupervisionCCReport { + validatePayload(raw.payload.length >= 3); + const moreUpdatesFollow = !!(raw.payload[0] & 0b1_0_000000); + const requestWakeUpOnDemand = !!(raw.payload[0] & 0b0_1_000000); + const sessionId = raw.payload[0] & 0b111111; + const status: SupervisionStatus = raw.payload[1]; + + if (status === SupervisionStatus.Working) { + const duration = Duration.parseReport(raw.payload[2]) + ?? new Duration(0, "seconds"); + return new SupervisionCCReport({ + nodeId: ctx.sourceNodeId, + moreUpdatesFollow, + requestWakeUpOnDemand, + sessionId, + status, + duration, + }); + } else { + return new SupervisionCCReport({ + nodeId: ctx.sourceNodeId, + moreUpdatesFollow, + requestWakeUpOnDemand, + sessionId, + status, + }); } } @@ -316,7 +338,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 +354,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 +367,7 @@ export class SupervisionCCReport extends SupervisionCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -365,9 +387,10 @@ export class SupervisionCCReport extends SupervisionCC { } // @publicAPI -export interface SupervisionCCGetOptions extends CCCommandOptions { +export interface SupervisionCCGetOptions { requestStatusUpdates: boolean; encapsulated: CommandClass; + sessionId: number; } function testResponseForSupervisionCCGet( @@ -381,35 +404,37 @@ function testResponseForSupervisionCCGet( @expectedCCResponse(SupervisionCCReport, testResponseForSupervisionCCGet) export class SupervisionCCGet extends SupervisionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | SupervisionCCGetOptions, + options: WithAddress, ) { - super(host, 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, { - data: this.payload.subarray(2), - fromEncapsulation: true, - encapCC: this, - origin: options.origin, - }); - } else { - this.sessionId = host.getNextSupervisionSessionId(this.nodeId); - this.requestStatusUpdates = options.requestStatusUpdates; - this.encapsulated = options.encapsulated; - options.encapsulated.encapsulatingCC = this as any; - } + super(options); + this.sessionId = options.sessionId; + this.requestStatusUpdates = options.requestStatusUpdates; + this.encapsulated = options.encapsulated; + // Supervision is inside MultiChannel CCs, so the endpoint must be copied + this.encapsulated.endpointIndex = this.endpointIndex; + this.encapsulated.encapsulatingCC = this as any; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): SupervisionCCGet { + validatePayload(raw.payload.length >= 3); + const requestStatusUpdates = !!(raw.payload[0] & 0b1_0_000000); + const sessionId = raw.payload[0] & 0b111111; + + const encapsulated = CommandClass.parse(raw.payload.subarray(2), ctx); + return new SupervisionCCGet({ + nodeId: ctx.sourceNodeId, + requestStatusUpdates, + sessionId, + encapsulated, + }); } public requestStatusUpdates: boolean; 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 +443,7 @@ export class SupervisionCCGet extends SupervisionCC { ]), encapCC, ]); - return super.serialize(); + return super.serialize(ctx); } protected computeEncapsulationOverhead(): number { @@ -426,9 +451,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..d23c88dea711 100644 --- a/packages/cc/src/cc/ThermostatFanModeCC.ts +++ b/packages/cc/src/cc/ThermostatFanModeCC.ts @@ -6,6 +6,7 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, enumValuesToMetadataStates, @@ -14,9 +15,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -30,10 +31,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -175,11 +177,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanModeCCReport >( cc, @@ -200,13 +202,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, + endpointIndex: 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 +219,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanModeCCSupportedReport >( cc, @@ -237,25 +239,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 +275,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 +289,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 +325,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", @@ -329,69 +335,91 @@ export class ThermostatFanModeCC extends CommandClass { } // @publicAPI -export type ThermostatFanModeCCSetOptions = CCCommandOptions & { +export interface ThermostatFanModeCCSetOptions { mode: ThermostatFanMode; off?: boolean; -}; +} @CCCommand(ThermostatFanModeCommand.Set) @useSupervision() export class ThermostatFanModeCCSet extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatFanModeCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.mode = options.mode; - this.off = options.off; - } + super(options); + this.mode = options.mode; + this.off = options.off; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): ThermostatFanModeCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ThermostatFanModeCCSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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, }; } } +// @publicAPI +export interface ThermostatFanModeCCReportOptions { + mode: ThermostatFanMode; + off?: boolean; +} + @CCCommand(ThermostatFanModeCommand.Report) export class ThermostatFanModeCCReport extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 1); - this.mode = this.payload[0] & 0b1111; + // TODO: Check implementation: + this.mode = options.mode; + this.off = options.off; + } - if (this.version >= 3) { - this.off = !!(this.payload[0] & 0b1000_0000); - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatFanModeCCReport { + validatePayload(raw.payload.length >= 1); + const mode: ThermostatFanMode = raw.payload[0] & 0b1111; + // V3+ + const off = !!(raw.payload[0] & 0b1000_0000); + + return new ThermostatFanModeCCReport({ + nodeId: ctx.sourceNodeId, + mode, + off, + }); } @ccValue(ThermostatFanModeCCValues.fanMode) @@ -400,7 +428,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 +436,7 @@ export class ThermostatFanModeCCReport extends ThermostatFanModeCC { message.off = this.off; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -418,25 +446,43 @@ export class ThermostatFanModeCCReport extends ThermostatFanModeCC { @expectedCCResponse(ThermostatFanModeCCReport) export class ThermostatFanModeCCGet extends ThermostatFanModeCC {} +// @publicAPI +export interface ThermostatFanModeCCSupportedReportOptions { + supportedModes: ThermostatFanMode[]; +} + @CCCommand(ThermostatFanModeCommand.SupportedReport) export class ThermostatFanModeCCSupportedReport extends ThermostatFanModeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - this.supportedModes = parseBitMask( - this.payload, + super(options); + + // TODO: Check implementation: + this.supportedModes = options.supportedModes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatFanModeCCSupportedReport { + const supportedModes: ThermostatFanMode[] = parseBitMask( + raw.payload, ThermostatFanMode["Auto low"], ); + + return new ThermostatFanModeCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedModes, + }); } - 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 +496,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..52eccf068a2d 100644 --- a/packages/cc/src/cc/ThermostatFanStateCC.ts +++ b/packages/cc/src/cc/ThermostatFanStateCC.ts @@ -5,14 +5,11 @@ import { MessagePriority, type MessageRecord, ValueMetadata, + type WithAddress, enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -21,8 +18,10 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -81,11 +80,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatFanStateCCReport >( cc, @@ -103,41 +102,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), @@ -147,27 +150,44 @@ export class ThermostatFanStateCC extends CommandClass { } } +// @publicAPI +export interface ThermostatFanStateCCReportOptions { + state: ThermostatFanState; +} + @CCCommand(ThermostatFanStateCommand.Report) export class ThermostatFanStateCCReport extends ThermostatFanStateCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.state = options.state; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatFanStateCCReport { + validatePayload(raw.payload.length == 1); + const state: ThermostatFanState = raw.payload[0] & 0b1111; - validatePayload(this.payload.length == 1); - this.state = this.payload[0] & 0b1111; + return new ThermostatFanStateCCReport({ + nodeId: ctx.sourceNodeId, + state, + }); } @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..5a7a15185c45 100644 --- a/packages/cc/src/cc/ThermostatModeCC.ts +++ b/packages/cc/src/cc/ThermostatModeCC.ts @@ -6,6 +6,7 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeBitMask, @@ -15,9 +16,9 @@ import { validatePayload, } 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 { validateArgs } from "@zwave-js/transformers"; @@ -31,10 +32,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -131,11 +133,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatModeCCReport >( cc, @@ -181,13 +183,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, + endpointIndex: 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 +200,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatModeCCSupportedReport >( cc, @@ -218,25 +220,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 +255,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 +270,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), @@ -303,55 +309,60 @@ export class ThermostatModeCC extends CommandClass { // @publicAPI export type ThermostatModeCCSetOptions = - & CCCommandOptions - & ( - | { - mode: Exclude< - ThermostatMode, - (typeof ThermostatMode)["Manufacturer specific"] - >; - } - | { - mode: (typeof ThermostatMode)["Manufacturer specific"]; - manufacturerData: Buffer; - } - ); + | { + mode: Exclude< + ThermostatMode, + (typeof ThermostatMode)["Manufacturer specific"] + >; + } + | { + mode: (typeof ThermostatMode)["Manufacturer specific"]; + manufacturerData: Buffer; + }; @CCCommand(ThermostatModeCommand.Set) @useSupervision() export class ThermostatModeCCSet extends ThermostatModeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatModeCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const manufacturerDataLength = (this.payload[0] >>> 5) & 0b111; - this.mode = this.payload[0] & 0b11111; - if (manufacturerDataLength > 0) { - validatePayload( - this.payload.length >= 1 + manufacturerDataLength, - ); - this.manufacturerData = this.payload.subarray( - 1, - 1 + manufacturerDataLength, - ); - } - } else { - this.mode = options.mode; - if ("manufacturerData" in options) { - this.manufacturerData = options.manufacturerData; - } + super(options); + this.mode = options.mode; + if ("manufacturerData" in options) { + this.manufacturerData = options.manufacturerData; + } + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ThermostatModeCCSet { + validatePayload(raw.payload.length >= 1); + const mode: ThermostatMode = raw.payload[0] & 0b11111; + if (mode !== ThermostatMode["Manufacturer specific"]) { + return new ThermostatModeCCSet({ + nodeId: ctx.sourceNodeId, + mode, + }); } + + const manufacturerDataLength = (raw.payload[0] >>> 5) & 0b111; + validatePayload( + raw.payload.length >= 1 + manufacturerDataLength, + ); + const manufacturerData = raw.payload.subarray( + 1, + 1 + manufacturerDataLength, + ); + + return new ThermostatModeCCSet({ + nodeId: ctx.sourceNodeId, + mode, + manufacturerData, + }); } 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 +375,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 +386,7 @@ export class ThermostatModeCCSet extends ThermostatModeCC { message["manufacturer data"] = buffer2hex(this.manufacturerData); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -383,56 +394,62 @@ export class ThermostatModeCCSet extends ThermostatModeCC { // @publicAPI export type ThermostatModeCCReportOptions = - & CCCommandOptions - & ( - | { - mode: Exclude< - ThermostatMode, - (typeof ThermostatMode)["Manufacturer specific"] - >; - manufacturerData?: undefined; - } - | { - mode: (typeof ThermostatMode)["Manufacturer specific"]; - manufacturerData?: Buffer; - } - ); + | { + mode: Exclude< + ThermostatMode, + (typeof ThermostatMode)["Manufacturer specific"] + >; + manufacturerData?: undefined; + } + | { + mode: (typeof ThermostatMode)["Manufacturer specific"]; + manufacturerData?: Buffer; + }; @CCCommand(ThermostatModeCommand.Report) export class ThermostatModeCCReport extends ThermostatModeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatModeCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.mode = this.payload[0] & 0b11111; + super(options); - if (this.version >= 3) { - const manufacturerDataLength = this.payload[0] >>> 5; + this.mode = options.mode; + this.manufacturerData = options.manufacturerData; + } - validatePayload( - this.payload.length >= 1 + manufacturerDataLength, - ); - if (manufacturerDataLength) { - this.manufacturerData = this.payload.subarray( - 1, - 1 + manufacturerDataLength, - ); - } - } - } else { - this.mode = options.mode; - this.manufacturerData = options.manufacturerData; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatModeCCReport { + validatePayload(raw.payload.length >= 1); + const mode: ThermostatMode = raw.payload[0] & 0b11111; + + if (mode !== ThermostatMode["Manufacturer specific"]) { + return new ThermostatModeCCReport({ + nodeId: ctx.sourceNodeId, + mode, + }); } + + // V3+ + const manufacturerDataLength = raw.payload[0] >>> 5; + validatePayload( + raw.payload.length >= 1 + manufacturerDataLength, + ); + const manufacturerData = raw.payload.subarray( + 1, + 1 + manufacturerDataLength, + ); + + return new ThermostatModeCCReport({ + nodeId: ctx.sourceNodeId, + mode, + manufacturerData, + }); } - 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 +457,7 @@ export class ThermostatModeCCReport extends ThermostatModeCC { const supportedModesValue = ThermostatModeCCValues.supportedModes; const supportedModes = this.getValue( - applHost, + ctx, supportedModesValue, ); @@ -452,14 +469,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 +487,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 +503,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 +514,7 @@ export class ThermostatModeCCReport extends ThermostatModeCC { message["manufacturer data"] = buffer2hex(this.manufacturerData); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -508,37 +525,40 @@ export class ThermostatModeCCReport extends ThermostatModeCC { export class ThermostatModeCCGet extends ThermostatModeCC {} // @publicAPI -export interface ThermostatModeCCSupportedReportOptions - extends CCCommandOptions -{ +export interface ThermostatModeCCSupportedReportOptions { supportedModes: ThermostatMode[]; } @CCCommand(ThermostatModeCommand.SupportedReport) export class ThermostatModeCCSupportedReport extends ThermostatModeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatModeCCSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.supportedModes = parseBitMask( - this.payload, - ThermostatMode.Off, - ); - } else { - this.supportedModes = options.supportedModes; - } + super(options); + this.supportedModes = options.supportedModes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatModeCCSupportedReport { + const supportedModes: ThermostatMode[] = parseBitMask( + raw.payload, + ThermostatMode.Off, + ); + + return new ThermostatModeCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedModes, + }); } - 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 +572,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..3987e436fedf 100644 --- a/packages/cc/src/cc/ThermostatOperatingStateCC.ts +++ b/packages/cc/src/cc/ThermostatOperatingStateCC.ts @@ -1,4 +1,4 @@ -import type { MessageOrCCLogEntry } from "@zwave-js/core/safe"; +import type { MessageOrCCLogEntry, WithAddress } from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, @@ -7,11 +7,7 @@ import { enumValuesToMetadataStates, validatePayload, } from "@zwave-js/core/safe"; -import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, -} from "@zwave-js/host/safe"; +import type { CCParsingContext, GetValueDB } from "@zwave-js/host/safe"; import { getEnumMemberName } from "@zwave-js/shared/safe"; import { CCAPI, @@ -21,8 +17,10 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { + type CCRaw, 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, + endpointIndex: 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( @@ -156,26 +158,43 @@ export class ThermostatOperatingStateCC extends CommandClass { } } +// @publicAPI +export interface ThermostatOperatingStateCCReportOptions { + state: ThermostatOperatingState; +} + @CCCommand(ThermostatOperatingStateCommand.Report) export class ThermostatOperatingStateCCReport extends ThermostatOperatingStateCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.state = options.state; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatOperatingStateCCReport { + validatePayload(raw.payload.length >= 1); + const state: ThermostatOperatingState = raw.payload[0]; - validatePayload(this.payload.length >= 1); - this.state = this.payload[0]; + return new ThermostatOperatingStateCCReport({ + nodeId: ctx.sourceNodeId, + state, + }); } @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..fab9ddada914 100644 --- a/packages/cc/src/cc/ThermostatSetbackCC.ts +++ b/packages/cc/src/cc/ThermostatSetbackCC.ts @@ -1,17 +1,16 @@ -import type { - MessageOrCCLogEntry, - SupervisionResult, -} from "@zwave-js/core/safe"; import { CommandClasses, type MaybeNotKnown, + type MessageOrCCLogEntry, MessagePriority, + type SupervisionResult, + type WithAddress, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -22,10 +21,10 @@ import { throwUnsupportedProperty, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -79,11 +78,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetbackCCReport >( cc, @@ -104,13 +103,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, + endpointIndex: this.endpoint.index, setbackType, setbackState, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -119,34 +118,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 +159,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", @@ -166,7 +169,7 @@ setback state: ${setbackResp.setbackState}`; } // @publicAPI -export interface ThermostatSetbackCCSetOptions extends CCCommandOptions { +export interface ThermostatSetbackCCSetOptions { setbackType: SetbackType; setbackState: SetbackState; } @@ -175,40 +178,47 @@ export interface ThermostatSetbackCCSetOptions extends CCCommandOptions { @useSupervision() export class ThermostatSetbackCCSet extends ThermostatSetbackCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetbackCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.setbackType = this.payload[0] & 0b11; - // If we receive an unknown setback state, return the raw value - const rawSetbackState = this.payload.readInt8(1); - this.setbackState = decodeSetbackState(rawSetbackState) - || rawSetbackState; - } else { - this.setbackType = options.setbackType; - this.setbackState = options.setbackState; - } + super(options); + this.setbackType = options.setbackType; + this.setbackState = options.setbackState; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetbackCCSet { + validatePayload(raw.payload.length >= 2); + const setbackType: SetbackType = raw.payload[0] & 0b11; + + // If we receive an unknown setback state, return the raw value + const rawSetbackState = raw.payload.readInt8(1); + const setbackState: SetbackState = decodeSetbackState(rawSetbackState) + || rawSetbackState; + + return new ThermostatSetbackCCSet({ + nodeId: ctx.sourceNodeId, + setbackType, + setbackState, + }); } public setbackType: SetbackType; /** 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,41 +241,48 @@ export interface ThermostatSetbackCCReportOptions { @CCCommand(ThermostatSetbackCommand.Report) export class ThermostatSetbackCCReport extends ThermostatSetbackCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & ThermostatSetbackCCReportOptions), + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.setbackType = this.payload[0] & 0b11; - // If we receive an unknown setback state, return the raw value - const rawSetbackState = this.payload.readInt8(1); - this.setbackState = decodeSetbackState(rawSetbackState) - || rawSetbackState; - } else { - this.setbackType = options.setbackType; - this.setbackState = options.setbackState; - } + super(options); + + this.setbackType = options.setbackType; + this.setbackState = options.setbackState; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetbackCCReport { + validatePayload(raw.payload.length >= 2); + const setbackType: SetbackType = raw.payload[0] & 0b11; + + // If we receive an unknown setback state, return the raw value + const rawSetbackState = raw.payload.readInt8(1); + const setbackState: SetbackState = decodeSetbackState(rawSetbackState) + || rawSetbackState; + + return new ThermostatSetbackCCReport({ + nodeId: ctx.sourceNodeId, + setbackType, + setbackState, + }); } public readonly setbackType: SetbackType; /** 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..3c39c854c610 100644 --- a/packages/cc/src/cc/ThermostatSetpointCC.ts +++ b/packages/cc/src/cc/ThermostatSetpointCC.ts @@ -7,6 +7,7 @@ import { type SupervisionResult, ValueMetadata, type ValueMetadataNumeric, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeBitMask, @@ -19,9 +20,9 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -35,10 +36,11 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../lib/CommandClass"; import { API, @@ -86,10 +88,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 +221,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, + endpointIndex: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCReport >( cc, @@ -256,14 +254,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, + endpointIndex: this.endpoint.index, setpointType, value, scale, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -274,12 +272,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, + endpointIndex: this.endpoint.index, setpointType, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCCapabilitiesReport >( cc, @@ -308,11 +306,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< ThermostatSetpointCCSupportedReport >( cc, @@ -329,7 +327,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 +337,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 +403,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 +440,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 +461,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 +479,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 +488,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 +520,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 +532,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", @@ -608,7 +543,7 @@ maximum value: ${setpointCaps.maxValue} ${maxValueUnit}`; } // @publicAPI -export interface ThermostatSetpointCCSetOptions extends CCCommandOptions { +export interface ThermostatSetpointCCSetOptions { setpointType: ThermostatSetpointType; value: number; scale: number; @@ -618,47 +553,53 @@ export interface ThermostatSetpointCCSetOptions extends CCCommandOptions { @useSupervision() export class ThermostatSetpointCCSet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.setpointType = this.payload[0] & 0b1111; - // parseFloatWithScale does its own validation - const { value, scale } = parseFloatWithScale( - this.payload.subarray(1), - ); - this.value = value; - this.scale = scale; - } else { - this.setpointType = options.setpointType; - this.value = options.value; - this.scale = options.scale; - } + super(options); + this.setpointType = options.setpointType; + this.value = options.value; + this.scale = options.scale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCSet { + validatePayload(raw.payload.length >= 1); + const setpointType: ThermostatSetpointType = raw.payload[0] & 0b1111; + + // parseFloatWithScale does its own validation + const { value, scale } = parseFloatWithScale( + raw.payload.subarray(1), + ); + + return new ThermostatSetpointCCSet({ + nodeId: ctx.sourceNodeId, + setpointType, + value, + scale, + }); } public setpointType: ThermostatSetpointType; 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, @@ -671,7 +612,7 @@ export class ThermostatSetpointCCSet extends ThermostatSetpointCC { } // @publicAPI -export interface ThermostatSetpointCCReportOptions extends CCCommandOptions { +export interface ThermostatSetpointCCReportOptions { type: ThermostatSetpointType; value: number; scale: number; @@ -680,59 +621,68 @@ export interface ThermostatSetpointCCReportOptions extends CCCommandOptions { @CCCommand(ThermostatSetpointCommand.Report) export class ThermostatSetpointCCReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.type = this.payload[0] & 0b1111; - if (this.type === 0) { - // Not supported - this.value = 0; - this.scale = 0; - return; - } + super(options); - // parseFloatWithScale does its own validation - const { value, scale } = parseFloatWithScale( - this.payload.subarray(1), - ); - this.value = value; - this.scale = scale; - } else { - this.type = options.type; - this.value = options.value; - this.scale = options.scale; + this.type = options.type; + this.value = options.value; + this.scale = options.scale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCReport { + validatePayload(raw.payload.length >= 1); + const type: ThermostatSetpointType = raw.payload[0] & 0b1111; + + if (type === 0) { + // Not supported + return new ThermostatSetpointCCReport({ + nodeId: ctx.sourceNodeId, + type, + value: 0, + scale: 0, + }); } + + // parseFloatWithScale does its own validation + const { value, scale } = parseFloatWithScale( + raw.payload.subarray(1), + ); + + return new ThermostatSetpointCCReport({ + nodeId: ctx.sourceNodeId, + type, + value, + 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); 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 +693,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, @@ -775,7 +725,7 @@ function testResponseForThermostatSetpointGet( } // @publicAPI -export interface ThermostatSetpointCCGetOptions extends CCCommandOptions { +export interface ThermostatSetpointCCGetOptions { setpointType: ThermostatSetpointType; } @@ -786,30 +736,35 @@ export interface ThermostatSetpointCCGetOptions extends CCCommandOptions { ) export class ThermostatSetpointCCGet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.setpointType = this.payload[0] & 0b1111; - } else { - this.setpointType = options.setpointType; - } + super(options); + this.setpointType = options.setpointType; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCGet { + validatePayload(raw.payload.length >= 1); + const setpointType: ThermostatSetpointType = raw.payload[0] & 0b1111; + + return new ThermostatSetpointCCGet({ + nodeId: ctx.sourceNodeId, + setpointType, + }); } 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, @@ -821,9 +776,7 @@ export class ThermostatSetpointCCGet extends ThermostatSetpointCC { } // @publicAPI -export interface ThermostatSetpointCCCapabilitiesReportOptions - extends CCCommandOptions -{ +export interface ThermostatSetpointCCCapabilitiesReportOptions { type: ThermostatSetpointType; minValue: number; minValueScale: number; @@ -836,40 +789,49 @@ export class ThermostatSetpointCCCapabilitiesReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCCapabilitiesReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.type = this.payload[0]; - let bytesRead: number; - // parseFloatWithScale does its own validation - ({ - value: this.minValue, - scale: this.minValueScale, - bytesRead, - } = parseFloatWithScale(this.payload.subarray(1))); - ({ value: this.maxValue, scale: this.maxValueScale } = - parseFloatWithScale(this.payload.subarray(1 + bytesRead))); - } else { - this.type = options.type; - this.minValue = options.minValue; - this.minValueScale = options.minValueScale; - this.maxValue = options.maxValue; - this.maxValueScale = options.maxValueScale; - } + super(options); + + this.type = options.type; + this.minValue = options.minValue; + this.minValueScale = options.minValueScale; + this.maxValue = options.maxValue; + this.maxValueScale = options.maxValueScale; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCCapabilitiesReport { + validatePayload(raw.payload.length >= 1); + const type: ThermostatSetpointType = raw.payload[0]; + // parseFloatWithScale does its own validation + const { + value: minValue, + scale: minValueScale, + bytesRead, + } = parseFloatWithScale(raw.payload.subarray(1)); + const { value: maxValue, scale: maxValueScale } = parseFloatWithScale( + raw.payload.subarray(1 + bytesRead), + ); + + return new ThermostatSetpointCCCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + type, + minValue, + minValueScale, + maxValue, + maxValueScale, + }); } - 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 +848,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, @@ -911,9 +873,7 @@ export class ThermostatSetpointCCCapabilitiesReport } // @publicAPI -export interface ThermostatSetpointCCCapabilitiesGetOptions - extends CCCommandOptions -{ +export interface ThermostatSetpointCCCapabilitiesGetOptions { setpointType: ThermostatSetpointType; } @@ -921,30 +881,35 @@ export interface ThermostatSetpointCCCapabilitiesGetOptions @expectedCCResponse(ThermostatSetpointCCCapabilitiesReport) export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCCapabilitiesGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.setpointType = this.payload[0] & 0b1111; - } else { - this.setpointType = options.setpointType; - } + super(options); + this.setpointType = options.setpointType; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCCapabilitiesGet { + validatePayload(raw.payload.length >= 1); + const setpointType: ThermostatSetpointType = raw.payload[0] & 0b1111; + + return new ThermostatSetpointCCCapabilitiesGet({ + nodeId: ctx.sourceNodeId, + setpointType, + }); } 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, @@ -956,64 +921,52 @@ export class ThermostatSetpointCCCapabilitiesGet extends ThermostatSetpointCC { } // @publicAPI -export interface ThermostatSetpointCCSupportedReportOptions - extends CCCommandOptions -{ +export interface ThermostatSetpointCCSupportedReportOptions { supportedSetpointTypes: ThermostatSetpointType[]; } @CCCommand(ThermostatSetpointCommand.SupportedReport) export class ThermostatSetpointCCSupportedReport extends ThermostatSetpointCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ThermostatSetpointCCSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const bitMask = this.payload; - const supported = parseBitMask( - bitMask, - ThermostatSetpointType["N/A"], + super(options); + + if (options.supportedSetpointTypes.length === 0) { + throw new ZWaveError( + `At least one setpoint type must be supported`, + ZWaveErrorCodes.Argument_Invalid, ); - 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. - } else { - if (options.supportedSetpointTypes.length === 0) { - throw new ZWaveError( - `At least one setpoint type must be supported`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.supportedSetpointTypes = options.supportedSetpointTypes; } + this.supportedSetpointTypes = options.supportedSetpointTypes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ThermostatSetpointCCSupportedReport { + validatePayload(raw.payload.length >= 1); + const bitMask = raw.payload; + const supported = parseBitMask( + bitMask, + ThermostatSetpointType["N/A"], + ); + // 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 + const supportedSetpointTypes: ThermostatSetpointType[] = supported.map( + (i) => thermostatSetpointTypeMap[i], + ); + + return new ThermostatSetpointCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedSetpointTypes, + }); } @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 +975,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..d0e0b0eba556 100644 --- a/packages/cc/src/cc/TimeCC.ts +++ b/packages/cc/src/cc/TimeCC.ts @@ -4,6 +4,7 @@ import { type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, + type WithAddress, ZWaveError, ZWaveErrorCodes, formatDate, @@ -12,19 +13,18 @@ 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 { pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; import { padStart } from "alcalzone-shared/strings"; import { CCAPI } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -62,11 +62,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -83,25 +83,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, + endpointIndex: 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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -118,14 +118,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, + endpointIndex: this.endpoint.index, year, month, day, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -134,25 +134,25 @@ 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, + endpointIndex: this.endpoint.index, standardOffset: timezone.standardOffset, dstOffset: timezone.dstOffset, 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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< TimeCCTimeOffsetReport >( cc, @@ -174,15 +174,15 @@ 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, + endpointIndex: this.endpoint.index, standardOffset: timezone.standardOffset, dstOffset: timezone.dstOffset, dstStart: timezone.startDate, dstEnd: timezone.endDate, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -191,18 +191,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 +212,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,12 +223,12 @@ export class TimeCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } // @publicAPI -export interface TimeCCTimeReportOptions extends CCCommandOptions { +export interface TimeCCTimeReportOptions { hour: number; minute: number; second: number; @@ -235,41 +237,53 @@ export interface TimeCCTimeReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.TimeReport) export class TimeCCTimeReport extends TimeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | TimeCCTimeReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.hour = this.payload[0] & 0b11111; - validatePayload(this.hour >= 0, this.hour <= 23); - this.minute = this.payload[1]; - validatePayload(this.minute >= 0, this.minute <= 59); - this.second = this.payload[2]; - validatePayload(this.second >= 0, this.second <= 59); - } else { - this.hour = options.hour; - this.minute = options.minute; - this.second = options.second; - } + super(options); + this.hour = options.hour; + this.minute = options.minute; + this.second = options.second; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): TimeCCTimeReport { + validatePayload(raw.payload.length >= 3); + const hour = raw.payload[0] & 0b11111; + const minute = raw.payload[1]; + const second = raw.payload[2]; + + validatePayload( + hour >= 0, + hour <= 23, + minute >= 0, + minute <= 59, + second >= 0, + second <= 59, + ); + + return new TimeCCTimeReport({ + nodeId: ctx.sourceNodeId, + hour, + minute, + second, + }); } public hour: number; 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( @@ -288,7 +302,7 @@ export class TimeCCTimeReport extends TimeCC { export class TimeCCTimeGet extends TimeCC {} // @publicAPI -export interface TimeCCDateReportOptions extends CCCommandOptions { +export interface TimeCCDateReportOptions { year: number; month: number; day: number; @@ -297,27 +311,33 @@ export interface TimeCCDateReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.DateReport) export class TimeCCDateReport extends TimeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | TimeCCDateReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.year = this.payload.readUInt16BE(0); - this.month = this.payload[2]; - this.day = this.payload[3]; - } else { - this.year = options.year; - this.month = options.month; - this.day = options.day; - } + super(options); + this.year = options.year; + this.month = options.month; + this.day = options.day; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): TimeCCDateReport { + validatePayload(raw.payload.length >= 4); + const year = raw.payload.readUInt16BE(0); + const month = raw.payload[2]; + const day = raw.payload[3]; + + return new TimeCCDateReport({ + nodeId: ctx.sourceNodeId, + year, + month, + day, + }); } public year: number; 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 +346,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( @@ -350,7 +370,7 @@ export class TimeCCDateReport extends TimeCC { export class TimeCCDateGet extends TimeCC {} // @publicAPI -export interface TimeCCTimeOffsetSetOptions extends CCCommandOptions { +export interface TimeCCTimeOffsetSetOptions { standardOffset: number; dstOffset: number; dstStart: Date; @@ -361,24 +381,28 @@ export interface TimeCCTimeOffsetSetOptions extends CCCommandOptions { @useSupervision() export class TimeCCTimeOffsetSet extends TimeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TimeCCTimeOffsetSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.standardOffset = options.standardOffset; - this.dstOffset = options.dstOffset; - this.dstStartDate = options.dstStart; - this.dstEndDate = options.dstEnd; - } + super(options); + this.standardOffset = options.standardOffset; + this.dstOffset = options.dstOffset; + this.dstStartDate = options.dstStart; + this.dstEndDate = options.dstEnd; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): TimeCCTimeOffsetSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new TimeCCTimeOffsetSet({ + // nodeId: ctx.sourceNodeId, + // }); } public standardOffset: number; @@ -386,7 +410,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 +425,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`, @@ -418,7 +442,7 @@ export class TimeCCTimeOffsetSet extends TimeCC { } // @publicAPI -export interface TimeCCTimeOffsetReportOptions extends CCCommandOptions { +export interface TimeCCTimeOffsetReportOptions { standardOffset: number; dstOffset: number; dstStart: Date; @@ -428,41 +452,46 @@ export interface TimeCCTimeOffsetReportOptions extends CCCommandOptions { @CCCommand(TimeCommand.TimeOffsetReport) export class TimeCCTimeOffsetReport extends TimeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TimeCCTimeOffsetReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 9); - const { standardOffset, dstOffset } = parseTimezone(this.payload); - this.standardOffset = standardOffset; - this.dstOffset = dstOffset; - - const currentYear = new Date().getUTCFullYear(); - this.dstStartDate = new Date( - Date.UTC( - currentYear, - this.payload[3] - 1, - this.payload[4], - this.payload[5], - ), - ); - this.dstEndDate = new Date( - Date.UTC( - currentYear, - this.payload[6] - 1, - this.payload[7], - this.payload[8], - ), - ); - } else { - this.standardOffset = options.standardOffset; - this.dstOffset = options.dstOffset; - this.dstStartDate = options.dstStart; - this.dstEndDate = options.dstEnd; - } + super(options); + this.standardOffset = options.standardOffset; + this.dstOffset = options.dstOffset; + this.dstStartDate = options.dstStart; + this.dstEndDate = options.dstEnd; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TimeCCTimeOffsetReport { + validatePayload(raw.payload.length >= 9); + const { standardOffset, dstOffset } = parseTimezone(raw.payload); + const currentYear = new Date().getUTCFullYear(); + const dstStartDate: Date = new Date( + Date.UTC( + currentYear, + raw.payload[3] - 1, + raw.payload[4], + raw.payload[5], + ), + ); + const dstEndDate: Date = new Date( + Date.UTC( + currentYear, + raw.payload[6] - 1, + raw.payload[7], + raw.payload[8], + ), + ); + + return new TimeCCTimeOffsetReport({ + nodeId: ctx.sourceNodeId, + standardOffset, + dstOffset, + dstStart: dstStartDate, + dstEnd: dstEndDate, + }); } public standardOffset: number; @@ -470,7 +499,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 +514,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..bef39dba2b50 100644 --- a/packages/cc/src/cc/TimeParametersCC.ts +++ b/packages/cc/src/cc/TimeParametersCC.ts @@ -1,18 +1,24 @@ import { CommandClasses, - type IZWaveEndpoint, type MessageOrCCLogEntry, MessagePriority, type SupervisionResult, ValueMetadata, + type WithAddress, 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, + CCParsingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { validateArgs } from "@zwave-js/transformers"; import { @@ -25,10 +31,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, } from "../lib/CommandClass"; import { API, @@ -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, + endpointIndex: 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, + endpointIndex: 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,54 +251,69 @@ export class TimeParametersCC extends CommandClass { await api.set(new Date()); // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } +// @publicAPI +export interface TimeParametersCCReportOptions { + dateAndTime: Date; +} + @CCCommand(TimeParametersCommand.Report) export class TimeParametersCCReport extends TimeParametersCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 7); + super(options); + + // TODO: Check implementation: + this.dateAndTime = options.dateAndTime; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TimeParametersCCReport { + validatePayload(raw.payload.length >= 7); const dateSegments = { - year: this.payload.readUInt16BE(0), - month: this.payload[2], - day: this.payload[3], - hour: this.payload[4], - minute: this.payload[5], - second: this.payload[6], + year: raw.payload.readUInt16BE(0), + month: raw.payload[2], + day: raw.payload[3], + hour: raw.payload[4], + minute: raw.payload[5], + second: raw.payload[6], }; - this._dateAndTime = segmentsToDate( + const dateAndTime: Date = segmentsToDate( dateSegments, // Assume we can use UTC and correct this assumption in persistValues false, ); + + return new TimeParametersCCReport({ + nodeId: ctx.sourceNodeId, + dateAndTime, + }); } - 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); + this.dateAndTime = segmentsToDate(segments, local); } - return super.persistValues(applHost); + return super.persistValues(ctx); } - private _dateAndTime: Date; @ccValue(TimeParametersCCValues.dateAndTime) - public get dateAndTime(): Date { - return this._dateAndTime; - } + public dateAndTime: Date; - public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry { + public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message: { "date and time": formatDate( this.dateAndTime, @@ -306,7 +329,7 @@ export class TimeParametersCCReport extends TimeParametersCC { export class TimeParametersCCGet extends TimeParametersCC {} // @publicAPI -export interface TimeParametersCCSetOptions extends CCCommandOptions { +export interface TimeParametersCCSetOptions { dateAndTime: Date; useLocalTime?: boolean; } @@ -315,58 +338,60 @@ export interface TimeParametersCCSetOptions extends CCCommandOptions { @useSupervision() export class TimeParametersCCSet extends TimeParametersCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TimeParametersCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 7); - const dateSegments = { - year: this.payload.readUInt16BE(0), - month: this.payload[2], - day: this.payload[3], - hour: this.payload[4], - minute: this.payload[5], - second: this.payload[6], - }; - validatePayload( - dateSegments.month >= 1 && dateSegments.month <= 12, - dateSegments.day >= 1 && dateSegments.day <= 31, - dateSegments.hour >= 0 && dateSegments.hour <= 23, - dateSegments.minute >= 0 && dateSegments.minute <= 59, - dateSegments.second >= 0 && dateSegments.second <= 59, - ); - this.dateAndTime = segmentsToDate( - dateSegments, - // Assume we can use UTC and correct this assumption in persistValues - false, - ); - } else { - this.dateAndTime = options.dateAndTime; - this.useLocalTime = options.useLocalTime; - } + super(options); + this.dateAndTime = options.dateAndTime; + this.useLocalTime = options.useLocalTime; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): TimeParametersCCSet { + validatePayload(raw.payload.length >= 7); + const dateSegments = { + year: raw.payload.readUInt16BE(0), + month: raw.payload[2], + day: raw.payload[3], + hour: raw.payload[4], + minute: raw.payload[5], + second: raw.payload[6], + }; + validatePayload( + dateSegments.month >= 1 && dateSegments.month <= 12, + dateSegments.day >= 1 && dateSegments.day <= 31, + dateSegments.hour >= 0 && dateSegments.hour <= 23, + dateSegments.minute >= 0 && dateSegments.minute <= 59, + dateSegments.second >= 0 && dateSegments.second <= 59, + ); + const dateAndTime = segmentsToDate( + dateSegments, + // Assume we can use UTC and correct this assumption in persistValues + false, + ); + + return new TimeParametersCCSet({ + nodeId: ctx.sourceNodeId, + dateAndTime, + }); } - 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 +407,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..92f7b8f9cbcd 100644 --- a/packages/cc/src/cc/TransportServiceCC.ts +++ b/packages/cc/src/cc/TransportServiceCC.ts @@ -3,22 +3,21 @@ import { CommandClasses, type MessageOrCCLogEntry, type SinglecastCC, + type WithAddress, ZWaveError, ZWaveErrorCodes, 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 { - type CCCommandOptions, + type CCRaw, type CCResponseRole, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, } from "../lib/CommandClass"; import { CCCommand, @@ -56,42 +55,10 @@ export class TransportServiceCC extends CommandClass { declare ccCommand: TransportServiceCommand; declare nodeId: number; - - // Override the default helper method - public static getCCCommand(data: Buffer): number | undefined { - const originalCCCommand = super.getCCCommand(data)!; - // Transport Service only uses the higher 5 bits for the command - return originalCCCommand & 0b11111_000; - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - protected deserialize(data: Buffer) { - const ret = super.deserialize(data) as { - ccId: CommandClasses; - ccCommand: number; - payload: Buffer; - }; - // Transport Service re-uses the lower 3 bits of the ccCommand as payload - ret.payload = Buffer.concat([ - Buffer.from([ret.ccCommand & 0b111]), - ret.payload, - ]); - return ret; - } - - /** Encapsulates a command that should be sent in multiple segments */ - public static encapsulate( - _host: ZWaveHost, - _cc: CommandClass, - ): TransportServiceCC { - throw new Error("not implemented"); - } } // @publicAPI -export interface TransportServiceCCFirstSegmentOptions - extends CCCommandOptions -{ +export interface TransportServiceCCFirstSegmentOptions { datagramSize: number; sessionId: number; headerExtension?: Buffer | undefined; @@ -116,53 +83,62 @@ export function isTransportServiceEncapsulation( // @expectedCCResponse(TransportServiceCCReport) export class TransportServiceCCFirstSegment extends TransportServiceCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TransportServiceCCFirstSegmentOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // Deserialization has already split the datagram size from the ccCommand. - // Therefore we have one more payload byte + super(options); + this.datagramSize = options.datagramSize; + this.sessionId = options.sessionId; + this.headerExtension = options.headerExtension; + this.partialDatagram = options.partialDatagram; + } - validatePayload(this.payload.length >= 6); // 2 bytes dgram size, 1 byte sessid/ext, 1+ bytes payload, 2 bytes checksum + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TransportServiceCCFirstSegment { + // Deserialization has already split the datagram size from the ccCommand. + // Therefore we have one more payload byte - // Verify the CRC - const headerBuffer = Buffer.from([ - this.ccId, - this.ccCommand | this.payload[0], - ]); - const ccBuffer = this.payload.subarray(1, -2); - let expectedCRC = CRC16_CCITT(headerBuffer); - expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); - const actualCRC = this.payload.readUInt16BE( - this.payload.length - 2, - ); - validatePayload(expectedCRC === actualCRC); - - this.datagramSize = this.payload.readUInt16BE(0); - this.sessionId = this.payload[2] >>> 4; - let payloadOffset = 3; - - // If there is a header extension, read it - const hasHeaderExtension = !!(this.payload[2] & 0b1000); - if (hasHeaderExtension) { - const extLength = this.payload[3]; - this.headerExtension = this.payload.subarray(4, 4 + extLength); - payloadOffset += 1 + extLength; - } + validatePayload(raw.payload.length >= 6); // 2 bytes dgram size, 1 byte sessid/ext, 1+ bytes payload, 2 bytes checksum - this.partialDatagram = this.payload.subarray(payloadOffset, -2); - // A node supporting the Transport Service Command Class, version 2 - // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. - validatePayload(this.partialDatagram.length <= MAX_SEGMENT_SIZE); - } else { - this.datagramSize = options.datagramSize; - this.sessionId = options.sessionId; - this.headerExtension = options.headerExtension; - this.partialDatagram = options.partialDatagram; + // Verify the CRC + const headerBuffer = Buffer.from([ + CommandClasses["Transport Service"], + TransportServiceCommand.FirstSegment | raw.payload[0], + ]); + const ccBuffer = raw.payload.subarray(1, -2); + let expectedCRC = CRC16_CCITT(headerBuffer); + expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); + const actualCRC = raw.payload.readUInt16BE( + raw.payload.length - 2, + ); + validatePayload(expectedCRC === actualCRC); + const datagramSize = raw.payload.readUInt16BE(0); + const sessionId = raw.payload[2] >>> 4; + let payloadOffset = 3; + + // If there is a header extension, read it + const hasHeaderExtension = !!(raw.payload[2] & 0b1000); + let headerExtension: Buffer | undefined; + + if (hasHeaderExtension) { + const extLength = raw.payload[3]; + headerExtension = raw.payload.subarray(4, 4 + extLength); + payloadOffset += 1 + extLength; } + const partialDatagram: Buffer = raw.payload.subarray(payloadOffset, -2); + + // A node supporting the Transport Service Command Class, version 2 + // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. + validatePayload(partialDatagram.length <= MAX_SEGMENT_SIZE); + + return new TransportServiceCCFirstSegment({ + nodeId: ctx.sourceNodeId, + datagramSize, + sessionId, + headerExtension, + partialDatagram, + }); } public datagramSize: number; @@ -171,7 +147,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 +178,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 +201,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,56 +225,66 @@ export interface TransportServiceCCSubsequentSegmentOptions // @expectedCCResponse(TransportServiceCCReport) export class TransportServiceCCSubsequentSegment extends TransportServiceCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TransportServiceCCSubsequentSegmentOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // Deserialization has already split the datagram size from the ccCommand. - // Therefore we have one more payload byte + super(options); + this.datagramSize = options.datagramSize; + this.datagramOffset = options.datagramOffset; + this.sessionId = options.sessionId; + this.headerExtension = options.headerExtension; + this.partialDatagram = options.partialDatagram; + } - validatePayload(this.payload.length >= 7); // 2 bytes dgram size, 1 byte sessid/ext/offset, 1 byte offset, 1+ bytes payload, 2 bytes checksum + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TransportServiceCCSubsequentSegment { + // Deserialization has already split the datagram size from the ccCommand. + // Therefore we have one more payload byte - // Verify the CRC - const headerBuffer = Buffer.from([ - this.ccId, - this.ccCommand | this.payload[0], - ]); - const ccBuffer = this.payload.subarray(1, -2); - let expectedCRC = CRC16_CCITT(headerBuffer); - expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); - const actualCRC = this.payload.readUInt16BE( - this.payload.length - 2, - ); - validatePayload(expectedCRC === actualCRC); - - this.datagramSize = this.payload.readUInt16BE(0); - this.sessionId = this.payload[2] >>> 4; - this.datagramOffset = ((this.payload[2] & 0b111) << 8) - + this.payload[3]; - let payloadOffset = 4; - - // If there is a header extension, read it - const hasHeaderExtension = !!(this.payload[2] & 0b1000); - if (hasHeaderExtension) { - const extLength = this.payload[4]; - this.headerExtension = this.payload.subarray(5, 5 + extLength); - payloadOffset += 1 + extLength; - } + validatePayload(raw.payload.length >= 7); // 2 bytes dgram size, 1 byte sessid/ext/offset, 1 byte offset, 1+ bytes payload, 2 bytes checksum - this.partialDatagram = this.payload.subarray(payloadOffset, -2); - // A node supporting the Transport Service Command Class, version 2 - // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. - validatePayload(this.partialDatagram.length <= MAX_SEGMENT_SIZE); - } else { - this.datagramSize = options.datagramSize; - this.datagramOffset = options.datagramOffset; - this.sessionId = options.sessionId; - this.headerExtension = options.headerExtension; - this.partialDatagram = options.partialDatagram; + // Verify the CRC + const headerBuffer = Buffer.from([ + CommandClasses["Transport Service"], + TransportServiceCommand.SubsequentSegment | raw.payload[0], + ]); + const ccBuffer = raw.payload.subarray(1, -2); + let expectedCRC = CRC16_CCITT(headerBuffer); + expectedCRC = CRC16_CCITT(ccBuffer, expectedCRC); + const actualCRC = raw.payload.readUInt16BE( + raw.payload.length - 2, + ); + validatePayload(expectedCRC === actualCRC); + const datagramSize = raw.payload.readUInt16BE(0); + const sessionId = raw.payload[2] >>> 4; + const datagramOffset = ((raw.payload[2] & 0b111) << 8) + + raw.payload[3]; + let payloadOffset = 4; + + // If there is a header extension, read it + const hasHeaderExtension = !!(raw.payload[2] & 0b1000); + let headerExtension: Buffer | undefined; + + if (hasHeaderExtension) { + const extLength = raw.payload[4]; + headerExtension = raw.payload.subarray(5, 5 + extLength); + payloadOffset += 1 + extLength; } + const partialDatagram: Buffer = raw.payload.subarray(payloadOffset, -2); + + // A node supporting the Transport Service Command Class, version 2 + // MUST NOT send Transport Service segments with the Payload field longer than 39 bytes. + validatePayload(partialDatagram.length <= MAX_SEGMENT_SIZE); + + return new TransportServiceCCSubsequentSegment({ + nodeId: ctx.sourceNodeId, + datagramSize, + sessionId, + datagramOffset, + headerExtension, + partialDatagram, + }); } public datagramSize: number; @@ -347,11 +333,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 +356,11 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { } // and deserialize the CC - this._encapsulated = CommandClass.from(this.host, { - data: datagram, - fromEncapsulation: true, - encapCC: this, - }); + this._encapsulated = CommandClass.parse(datagram, ctx); + this._encapsulated.encapsulatingCC = this as any; } - 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 +394,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 +408,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, @@ -441,9 +424,7 @@ export class TransportServiceCCSubsequentSegment extends TransportServiceCC { } // @publicAPI -export interface TransportServiceCCSegmentRequestOptions - extends CCCommandOptions -{ +export interface TransportServiceCCSegmentRequestOptions { sessionId: number; datagramOffset: number; } @@ -467,38 +448,44 @@ function testResponseForSegmentRequest( @expectedCCResponse(TransportServiceCC, testResponseForSegmentRequest) export class TransportServiceCCSegmentRequest extends TransportServiceCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TransportServiceCCSegmentRequestOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.sessionId = this.payload[1] >>> 4; - this.datagramOffset = ((this.payload[1] & 0b111) << 8) - + this.payload[2]; - } else { - this.sessionId = options.sessionId; - this.datagramOffset = options.datagramOffset; - } + super(options); + this.sessionId = options.sessionId; + this.datagramOffset = options.datagramOffset; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TransportServiceCCSegmentRequest { + validatePayload(raw.payload.length >= 3); + const sessionId = raw.payload[1] >>> 4; + const datagramOffset = ((raw.payload[1] & 0b111) << 8) + + raw.payload[2]; + + return new TransportServiceCCSegmentRequest({ + nodeId: ctx.sourceNodeId, + sessionId, + datagramOffset, + }); } 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, @@ -508,76 +495,84 @@ export class TransportServiceCCSegmentRequest extends TransportServiceCC { } // @publicAPI -export interface TransportServiceCCSegmentCompleteOptions - extends CCCommandOptions -{ +export interface TransportServiceCCSegmentCompleteOptions { sessionId: number; } @CCCommand(TransportServiceCommand.SegmentComplete) export class TransportServiceCCSegmentComplete extends TransportServiceCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TransportServiceCCSegmentCompleteOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.sessionId = this.payload[1] >>> 4; - } else { - this.sessionId = options.sessionId; - } + super(options); + this.sessionId = options.sessionId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TransportServiceCCSegmentComplete { + validatePayload(raw.payload.length >= 2); + const sessionId = raw.payload[1] >>> 4; + + return new TransportServiceCCSegmentComplete({ + nodeId: ctx.sourceNodeId, + sessionId, + }); } 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 }, }; } } // @publicAPI -export interface TransportServiceCCSegmentWaitOptions extends CCCommandOptions { +export interface TransportServiceCCSegmentWaitOptions { pendingSegments: number; } @CCCommand(TransportServiceCommand.SegmentWait) export class TransportServiceCCSegmentWait extends TransportServiceCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | TransportServiceCCSegmentWaitOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.pendingSegments = this.payload[1]; - } else { - this.pendingSegments = options.pendingSegments; - } + super(options); + this.pendingSegments = options.pendingSegments; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): TransportServiceCCSegmentWait { + validatePayload(raw.payload.length >= 2); + const pendingSegments = raw.payload[1]; + + return new TransportServiceCCSegmentWait({ + nodeId: ctx.sourceNodeId, + pendingSegments, + }); } 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..0e70a3b65001 100644 --- a/packages/cc/src/cc/UserCodeCC.ts +++ b/packages/cc/src/cc/UserCodeCC.ts @@ -1,12 +1,13 @@ import { CommandClasses, - type IZWaveEndpoint, + type EndpointId, type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, encodeBitMask, @@ -16,9 +17,10 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetSupportedCCVersion, + GetValueDB, } from "@zwave-js/host/safe"; import { getEnumMemberName, @@ -41,10 +43,12 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, + getEffectiveCCVersion, } from "../lib/CommandClass"; import { API, @@ -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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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,15 +548,15 @@ export class UserCodeCCAPI extends PhysicalCCAPI { ); } - const cc = new UserCodeCCSet(this.applHost, { + const cc = new UserCodeCCSet({ nodeId: this.endpoint.nodeId, - endpoint: this.endpoint.index, + endpointIndex: this.endpoint.index, userId, userIdStatus, 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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, + endpointIndex: 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,78 +1234,87 @@ export type UserCodeCCSetOptions = @useSupervision() export class UserCodeCCSet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & UserCodeCCSetOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.userIdStatus = this.payload[1]; - if ( - this.userIdStatus !== UserIDStatus.Available - && this.userIdStatus !== UserIDStatus.StatusNotAvailable - ) { - this.userCode = this.payload.subarray(2); - } else { - this.userCode = Buffer.alloc(4, 0x00); - } - } else { - this.userId = options.userId; - this.userIdStatus = options.userIdStatus; + super(options); + this.userId = options.userId; + this.userIdStatus = options.userIdStatus; - // Validate options - if (this.userId < 0) { - throw new ZWaveError( - `${this.constructor.name}: The user ID must be between greater than 0.`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if ( - this.userId === 0 - && this.userIdStatus !== UserIDStatus.Available - ) { + // Validate options + if (this.userId < 0) { + throw new ZWaveError( + `${this.constructor.name}: The user ID must be between greater than 0.`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if ( + this.userId === 0 + && this.userIdStatus !== UserIDStatus.Available + ) { + throw new ZWaveError( + `${this.constructor.name}: User ID 0 may only be used to clear all user codes`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (this.userIdStatus === UserIDStatus.Available) { + this.userCode = "\0".repeat(4); + } else { + this.userCode = options.userCode!; + // Specs say ASCII 0-9, manufacturers don't care :) + if (this.userCode.length < 4 || this.userCode.length > 10) { throw new ZWaveError( - `${this.constructor.name}: User ID 0 may only be used to clear all user codes`, + `${this.constructor.name}: The user code must have a length of 4 to 10 ${ + typeof this.userCode === "string" + ? "characters" + : "bytes" + }`, ZWaveErrorCodes.Argument_Invalid, ); - } else if (this.userIdStatus === UserIDStatus.Available) { - this.userCode = "\0".repeat(4); - } else { - this.userCode = options.userCode!; - // Specs say ASCII 0-9, manufacturers don't care :) - if (this.userCode.length < 4 || this.userCode.length > 10) { - throw new ZWaveError( - `${this.constructor.name}: The user code must have a length of 4 to 10 ${ - typeof this.userCode === "string" - ? "characters" - : "bytes" - }`, - ZWaveErrorCodes.Argument_Invalid, - ); - } } } } + public static from(raw: CCRaw, ctx: CCParsingContext): UserCodeCCSet { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const userIdStatus: UserIDStatus = raw.payload[1]; + if (userIdStatus === UserIDStatus.StatusNotAvailable) { + validatePayload.fail("Invalid user ID status"); + } + + if (userIdStatus === UserIDStatus.Available) { + return new UserCodeCCSet({ + nodeId: ctx.sourceNodeId, + userId, + userIdStatus, + }); + } + + const userCode = raw.payload.subarray(2); + + return new UserCodeCCSet({ + nodeId: ctx.sourceNodeId, + userId, + userIdStatus, + userCode, + }); + } + public userId: number; 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), @@ -1306,7 +1325,7 @@ export class UserCodeCCSet extends UserCodeCC { } // @publicAPI -export interface UserCodeCCReportOptions extends CCCommandOptions { +export interface UserCodeCCReportOptions { userId: number; userIdStatus: UserIDStatus; userCode?: string | Buffer; @@ -1317,64 +1336,68 @@ export class UserCodeCCReport extends UserCodeCC implements NotificationEventPayload { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | UserCodeCCReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userId = this.payload[0]; - this.userIdStatus = this.payload[1]; + this.userId = options.userId; + this.userIdStatus = options.userIdStatus; + this.userCode = options.userCode ?? ""; + } - if ( - this.payload.length === 2 - && (this.userIdStatus === UserIDStatus.Available - || this.userIdStatus === UserIDStatus.StatusNotAvailable) - ) { - // The user code is not set or not available and this report contains no user code - this.userCode = ""; - } else { - // The specs require the user code to be at least 4 digits - validatePayload(this.payload.length >= 6); + public static from(raw: CCRaw, ctx: CCParsingContext): UserCodeCCReport { + validatePayload(raw.payload.length >= 2); + const userId = raw.payload[0]; + const userIdStatus: UserIDStatus = raw.payload[1]; + let userCode: string | Buffer; - let userCodeBuffer = this.payload.subarray(2); - // Specs say infer user code from payload length, manufacturers send zero-padded strings - while (userCodeBuffer.at(-1) === 0) { - userCodeBuffer = userCodeBuffer.subarray(0, -1); - } - // Specs say ASCII 0-9, manufacturers don't care :) - // Thus we check if the code is printable using ASCII, if not keep it as a Buffer - const userCodeString = userCodeBuffer.toString("utf8"); - if (isPrintableASCII(userCodeString)) { - this.userCode = userCodeString; - } else if ( - this.version === 1 - && isPrintableASCIIWithWhitespace(userCodeString) - ) { - // Ignore leading and trailing whitespace in V1 reports if the rest is ASCII - this.userCode = userCodeString.trim(); - } else { - this.userCode = userCodeBuffer; - } - } + if ( + raw.payload.length === 2 + && (userIdStatus === UserIDStatus.Available + || userIdStatus === UserIDStatus.StatusNotAvailable) + ) { + // The user code is not set or not available and this report contains no user code + userCode = ""; } else { - this.userId = options.userId; - this.userIdStatus = options.userIdStatus; - this.userCode = options.userCode ?? ""; + // The specs require the user code to be at least 4 digits + validatePayload(raw.payload.length >= 6); + + let userCodeBuffer = raw.payload.subarray(2); + // Specs say infer user code from payload length, manufacturers send zero-padded strings + while (userCodeBuffer.at(-1) === 0) { + userCodeBuffer = userCodeBuffer.subarray(0, -1); + } + // Specs say ASCII 0-9, manufacturers don't care :) + // Thus we check if the code is printable using ASCII, if not keep it as a Buffer + const userCodeString = userCodeBuffer.toString("utf8"); + if (isPrintableASCII(userCodeString)) { + userCode = userCodeString; + } else if (isPrintableASCIIWithWhitespace(userCodeString)) { + // Ignore leading and trailing whitespace in V1 reports if the rest is ASCII + userCode = userCodeString.trim(); + } else { + userCode = userCodeBuffer; + } } + + return new UserCodeCCReport({ + nodeId: ctx.sourceNodeId, + userId, + userIdStatus, + userCode, + }); } public readonly userId: number; 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 +1405,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 +1417,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), @@ -1415,7 +1438,7 @@ export class UserCodeCCReport extends UserCodeCC } // @publicAPI -export interface UserCodeCCGetOptions extends CCCommandOptions { +export interface UserCodeCCGetOptions { userId: number; } @@ -1423,76 +1446,88 @@ export interface UserCodeCCGetOptions extends CCCommandOptions { @expectedCCResponse(UserCodeCCReport) export class UserCodeCCGet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | UserCodeCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.userId = this.payload[0]; - } else { - this.userId = options.userId; - } + super(options); + this.userId = options.userId; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): UserCodeCCGet { + validatePayload(raw.payload.length >= 1); + const userId = raw.payload[0]; + + return new UserCodeCCGet({ + nodeId: ctx.sourceNodeId, + userId, + }); } 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 }, }; } } // @publicAPI -export interface UserCodeCCUsersNumberReportOptions extends CCCommandOptions { +export interface UserCodeCCUsersNumberReportOptions { supportedUsers: number; } @CCCommand(UserCodeCommand.UsersNumberReport) export class UserCodeCCUsersNumberReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCUsersNumberReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - if (this.payload.length >= 3) { - // V2+ - this.supportedUsers = this.payload.readUInt16BE(1); - } else { - // V1 - this.supportedUsers = this.payload[0]; - } + this.supportedUsers = options.supportedUsers; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCUsersNumberReport { + validatePayload(raw.payload.length >= 1); + + let supportedUsers; + + if (raw.payload.length >= 3) { + // V2+ + supportedUsers = raw.payload.readUInt16BE(1); } else { - this.supportedUsers = options.supportedUsers; + // V1 + supportedUsers = raw.payload[0]; } + + return new UserCodeCCUsersNumberReport({ + nodeId: ctx.sourceNodeId, + supportedUsers, + }); } @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 }, }; } @@ -1503,7 +1538,7 @@ export class UserCodeCCUsersNumberReport extends UserCodeCC { export class UserCodeCCUsersNumberGet extends UserCodeCC {} // @publicAPI -export interface UserCodeCCCapabilitiesReportOptions extends CCCommandOptions { +export interface UserCodeCCCapabilitiesReportOptions { supportsAdminCode: boolean; supportsAdminCodeDeactivation: boolean; supportsUserCodeChecksum: boolean; @@ -1517,80 +1552,92 @@ export interface UserCodeCCCapabilitiesReportOptions extends CCCommandOptions { @CCCommand(UserCodeCommand.CapabilitiesReport) export class UserCodeCCCapabilitiesReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCCapabilitiesReportOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + this.supportsAdminCode = options.supportsAdminCode; + this.supportsAdminCodeDeactivation = + options.supportsAdminCodeDeactivation; + this.supportsUserCodeChecksum = options.supportsUserCodeChecksum; + this.supportsMultipleUserCodeReport = + options.supportsMultipleUserCodeReport; + this.supportsMultipleUserCodeSet = options.supportsMultipleUserCodeSet; + this.supportedUserIDStatuses = options.supportedUserIDStatuses; + this.supportedKeypadModes = options.supportedKeypadModes; + this.supportedASCIIChars = options.supportedASCIIChars; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCCapabilitiesReport { + let offset = 0; + + validatePayload(raw.payload.length >= offset + 1); + const supportsAdminCode = !!(raw.payload[offset] & 0b100_00000); + const supportsAdminCodeDeactivation = !!( + raw.payload[offset] & 0b010_00000 + ); + const statusBitMaskLength = raw.payload[offset] & 0b000_11111; + offset += 1; - if (gotDeserializationOptions(options)) { - let offset = 0; + validatePayload( + raw.payload.length >= offset + statusBitMaskLength + 1, + ); + const supportedUserIDStatuses: UserIDStatus[] = parseBitMask( + raw.payload.subarray(offset, offset + statusBitMaskLength), + UserIDStatus.Available, + ); - validatePayload(this.payload.length >= offset + 1); - this.supportsAdminCode = !!(this.payload[offset] & 0b100_00000); - this.supportsAdminCodeDeactivation = !!( - this.payload[offset] & 0b010_00000 - ); - const statusBitMaskLength = this.payload[offset] & 0b000_11111; - offset += 1; + offset += statusBitMaskLength; + const supportsUserCodeChecksum = !!( + raw.payload[offset] & 0b100_00000 + ); + const supportsMultipleUserCodeReport = !!( + raw.payload[offset] & 0b010_00000 + ); + const supportsMultipleUserCodeSet = !!( + raw.payload[offset] & 0b001_00000 + ); + const keypadModesBitMaskLength = raw.payload[offset] & 0b000_11111; + offset += 1; - validatePayload( - this.payload.length >= offset + statusBitMaskLength + 1, - ); - this.supportedUserIDStatuses = parseBitMask( - this.payload.subarray(offset, offset + statusBitMaskLength), - UserIDStatus.Available, - ); - offset += statusBitMaskLength; + validatePayload( + raw.payload.length >= offset + keypadModesBitMaskLength + 1, + ); + const supportedKeypadModes: KeypadMode[] = parseBitMask( + raw.payload.subarray( + offset, + offset + keypadModesBitMaskLength, + ), + KeypadMode.Normal, + ); - this.supportsUserCodeChecksum = !!( - this.payload[offset] & 0b100_00000 - ); - this.supportsMultipleUserCodeReport = !!( - this.payload[offset] & 0b010_00000 - ); - this.supportsMultipleUserCodeSet = !!( - this.payload[offset] & 0b001_00000 - ); - const keypadModesBitMaskLength = this.payload[offset] & 0b000_11111; - offset += 1; + offset += keypadModesBitMaskLength; - validatePayload( - this.payload.length >= offset + keypadModesBitMaskLength + 1, - ); - this.supportedKeypadModes = parseBitMask( - this.payload.subarray( - offset, - offset + keypadModesBitMaskLength, - ), - KeypadMode.Normal, - ); - offset += keypadModesBitMaskLength; + const keysBitMaskLength = raw.payload[offset] & 0b000_11111; + offset += 1; - const keysBitMaskLength = this.payload[offset] & 0b000_11111; - offset += 1; + validatePayload(raw.payload.length >= offset + keysBitMaskLength); + const supportedASCIIChars = Buffer.from( + parseBitMask( + raw.payload.subarray(offset, offset + keysBitMaskLength), + 0, + ), + ).toString("ascii"); - validatePayload(this.payload.length >= offset + keysBitMaskLength); - this.supportedASCIIChars = Buffer.from( - parseBitMask( - this.payload.subarray(offset, offset + keysBitMaskLength), - 0, - ), - ).toString("ascii"); - } else { - this.supportsAdminCode = options.supportsAdminCode; - this.supportsAdminCodeDeactivation = - options.supportsAdminCodeDeactivation; - this.supportsUserCodeChecksum = options.supportsUserCodeChecksum; - this.supportsMultipleUserCodeReport = - options.supportsMultipleUserCodeReport; - this.supportsMultipleUserCodeSet = - options.supportsMultipleUserCodeSet; - this.supportedUserIDStatuses = options.supportedUserIDStatuses; - this.supportedKeypadModes = options.supportedKeypadModes; - this.supportedASCIIChars = options.supportedASCIIChars; - } + return new UserCodeCCCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + supportsAdminCode, + supportsAdminCodeDeactivation, + supportedUserIDStatuses, + supportsUserCodeChecksum, + supportsMultipleUserCodeReport, + supportsMultipleUserCodeSet, + supportedKeypadModes, + supportedASCIIChars, + }); } @ccValue(UserCodeCCValues.supportsAdminCode) @@ -1617,7 +1664,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 +1698,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": @@ -1686,7 +1733,7 @@ export class UserCodeCCCapabilitiesReport extends UserCodeCC { export class UserCodeCCCapabilitiesGet extends UserCodeCC {} // @publicAPI -export interface UserCodeCCKeypadModeSetOptions extends CCCommandOptions { +export interface UserCodeCCKeypadModeSetOptions { keypadMode: KeypadMode; } @@ -1694,68 +1741,78 @@ export interface UserCodeCCKeypadModeSetOptions extends CCCommandOptions { @useSupervision() export class UserCodeCCKeypadModeSet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCKeypadModeSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.keypadMode = this.payload[0]; - } else { - this.keypadMode = options.keypadMode; - } + super(options); + this.keypadMode = options.keypadMode; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCKeypadModeSet { + validatePayload(raw.payload.length >= 1); + const keypadMode: KeypadMode = raw.payload[0]; + + return new UserCodeCCKeypadModeSet({ + nodeId: ctx.sourceNodeId, + keypadMode, + }); } 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) }, }; } } // @publicAPI -export interface UserCodeCCKeypadModeReportOptions extends CCCommandOptions { +export interface UserCodeCCKeypadModeReportOptions { keypadMode: KeypadMode; } @CCCommand(UserCodeCommand.KeypadModeReport) export class UserCodeCCKeypadModeReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCKeypadModeReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.keypadMode = this.payload[0]; - } else { - this.keypadMode = options.keypadMode; - } + super(options); + this.keypadMode = options.keypadMode; } - public persistValues(applHost: ZWaveApplicationHost): boolean { - if (!super.persistValues(applHost)) return false; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCKeypadModeReport { + validatePayload(raw.payload.length >= 1); + const keypadMode: KeypadMode = raw.payload[0]; + + return new UserCodeCCKeypadModeReport({ + nodeId: ctx.sourceNodeId, + keypadMode, + }); + } + + 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 +1826,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), }, @@ -1789,7 +1846,7 @@ export class UserCodeCCKeypadModeReport extends UserCodeCC { export class UserCodeCCKeypadModeGet extends UserCodeCC {} // @publicAPI -export interface UserCodeCCAdminCodeSetOptions extends CCCommandOptions { +export interface UserCodeCCAdminCodeSetOptions { adminCode: string; } @@ -1797,82 +1854,92 @@ export interface UserCodeCCAdminCodeSetOptions extends CCCommandOptions { @useSupervision() export class UserCodeCCAdminCodeSet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCAdminCodeSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const codeLength = this.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1 + codeLength); - this.adminCode = this.payload - .subarray(1, 1 + codeLength) - .toString("ascii"); - } else { - this.adminCode = options.adminCode; - } + super(options); + this.adminCode = options.adminCode; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCAdminCodeSet { + validatePayload(raw.payload.length >= 1); + const codeLength = raw.payload[0] & 0b1111; + validatePayload(raw.payload.length >= 1 + codeLength); + const adminCode = raw.payload + .subarray(1, 1 + codeLength) + .toString("ascii"); + + return new UserCodeCCAdminCodeSet({ + nodeId: ctx.sourceNodeId, + adminCode, + }); } 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) }, }; } } // @publicAPI -export interface UserCodeCCAdminCodeReportOptions extends CCCommandOptions { +export interface UserCodeCCAdminCodeReportOptions { adminCode: string; } @CCCommand(UserCodeCommand.AdminCodeReport) export class UserCodeCCAdminCodeReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCAdminCodeReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const codeLength = this.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1 + codeLength); - this.adminCode = this.payload - .subarray(1, 1 + codeLength) - .toString("ascii"); - } else { - this.adminCode = options.adminCode; - } + super(options); + this.adminCode = options.adminCode; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCAdminCodeReport { + validatePayload(raw.payload.length >= 1); + const codeLength = raw.payload[0] & 0b1111; + validatePayload(raw.payload.length >= 1 + codeLength); + const adminCode = raw.payload + .subarray(1, 1 + codeLength) + .toString("ascii"); + + return new UserCodeCCAdminCodeReport({ + nodeId: ctx.sourceNodeId, + adminCode, + }); } @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) }, }; } @@ -1883,41 +1950,44 @@ export class UserCodeCCAdminCodeReport extends UserCodeCC { export class UserCodeCCAdminCodeGet extends UserCodeCC {} // @publicAPI -export interface UserCodeCCUserCodeChecksumReportOptions - extends CCCommandOptions -{ +export interface UserCodeCCUserCodeChecksumReportOptions { userCodeChecksum: number; } @CCCommand(UserCodeCommand.UserCodeChecksumReport) export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCUserCodeChecksumReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.userCodeChecksum = this.payload.readUInt16BE(0); - } else { - this.userCodeChecksum = options.userCodeChecksum; - } + super(options); + this.userCodeChecksum = options.userCodeChecksum; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCUserCodeChecksumReport { + validatePayload(raw.payload.length >= 2); + const userCodeChecksum = raw.payload.readUInt16BE(0); + + return new UserCodeCCUserCodeChecksumReport({ + nodeId: ctx.sourceNodeId, + userCodeChecksum, + }); } @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) }, }; } @@ -1928,7 +1998,7 @@ export class UserCodeCCUserCodeChecksumReport extends UserCodeCC { export class UserCodeCCUserCodeChecksumGet extends UserCodeCC {} // @publicAPI -export interface UserCodeCCExtendedUserCodeSetOptions extends CCCommandOptions { +export interface UserCodeCCExtendedUserCodeSetOptions { userCodes: UserCodeCCSetOptions[]; } @@ -1942,26 +2012,30 @@ export interface UserCode { @useSupervision() export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCExtendedUserCodeSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.userCodes = options.userCodes; - } + super(options); + this.userCodes = options.userCodes; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): UserCodeCCExtendedUserCodeSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new UserCodeCCExtendedUserCodeSet({ + // nodeId: ctx.sourceNodeId, + // }); } 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 +2055,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,44 +2068,63 @@ export class UserCodeCCExtendedUserCodeSet extends UserCodeCC { } (status: ${getEnumMemberName(UserIDStatus, userIdStatus)})`; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface UserCodeCCExtendedUserCodeReportOptions { + userCodes: UserCode[]; + nextUserId: number; +} + @CCCommand(UserCodeCommand.ExtendedUserCodeReport) export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 1); - const numCodes = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.userCodes = options.userCodes; + this.nextUserId = options.nextUserId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): UserCodeCCExtendedUserCodeReport { + validatePayload(raw.payload.length >= 1); + const numCodes = raw.payload[0]; let offset = 1; const userCodes: UserCode[] = []; // parse each user code for (let i = 0; i < numCodes; i++) { const { code, bytesRead } = parseExtendedUserCode( - this.payload.subarray(offset), + raw.payload.subarray(offset), ); userCodes.push(code); offset += bytesRead; } - this.userCodes = userCodes; + validatePayload(raw.payload.length >= offset + 2); + const nextUserId = raw.payload.readUInt16BE(offset); - validatePayload(this.payload.length >= offset + 2); - this.nextUserId = this.payload.readUInt16BE(offset); + return new UserCodeCCExtendedUserCodeReport({ + nodeId: ctx.sourceNodeId, + userCodes, + nextUserId, + }); } - 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 +2136,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,14 +2147,14 @@ export class UserCodeCCExtendedUserCodeReport extends UserCodeCC { } message["next user id"] = this.nextUserId; return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface UserCodeCCExtendedUserCodeGetOptions extends CCCommandOptions { +export interface UserCodeCCExtendedUserCodeGetOptions { userId: number; reportMore?: boolean; } @@ -2070,36 +2163,40 @@ export interface UserCodeCCExtendedUserCodeGetOptions extends CCCommandOptions { @expectedCCResponse(UserCodeCCExtendedUserCodeReport) export class UserCodeCCExtendedUserCodeGet extends UserCodeCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | UserCodeCCExtendedUserCodeGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.userId = options.userId; - this.reportMore = !!options.reportMore; - } + super(options); + this.userId = options.userId; + this.reportMore = !!options.reportMore; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): UserCodeCCExtendedUserCodeGet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new UserCodeCCExtendedUserCodeGet({ + // nodeId: ctx.sourceNodeId, + // }); } 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..13c0ac0a251b 100644 --- a/packages/cc/src/cc/VersionCC.ts +++ b/packages/cc/src/cc/VersionCC.ts @@ -6,6 +6,7 @@ import { type MessageRecord, SecurityClass, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, ZWaveLibraryTypes, @@ -16,18 +17,17 @@ import { validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -226,15 +226,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 +243,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -272,12 +265,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, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } @validateArgs() @@ -289,12 +282,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, + endpointIndex: this.endpoint.index, requestedCC, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCCommandClassReport >( cc, @@ -330,13 +323,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, + endpointIndex: 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 +339,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCCapabilitiesReport >( cc, @@ -367,13 +360,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, + endpointIndex: 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 +376,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< VersionCCZWaveSoftwareReport >( cc, @@ -420,8 +413,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 +426,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 +448,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 +459,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 +523,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 +548,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 +567,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 +576,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 +600,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 +610,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 +620,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 +636,7 @@ export class VersionCC extends CommandClass { } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -662,57 +651,63 @@ export interface VersionCCReportOptions { @CCCommand(VersionCommand.Report) export class VersionCCReport extends VersionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (VersionCCReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, 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) { - this.hardwareVersion = this.payload[5]; - const additionalFirmwares = this.payload[6]; - validatePayload( - this.payload.length >= 7 + 2 * additionalFirmwares, - ); - for (let i = 0; i < additionalFirmwares; i++) { - this.firmwareVersions.push( - `${this.payload[7 + 2 * i]}.${ - this.payload[7 + 2 * i + 1] - }`, - ); - } - } - } else { - if (!/^\d+\.\d+(\.\d+)?$/.test(options.protocolVersion)) { - throw new ZWaveError( - `protocolVersion must be a string in the format "major.minor" or "major.minor.patch", received "${options.protocolVersion}"`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if ( - !options.firmwareVersions.every((fw) => - /^\d+\.\d+(\.\d+)?$/.test(fw) - ) - ) { - throw new ZWaveError( - `firmwareVersions must be an array of strings in the format "major.minor" or "major.minor.patch", received "${ - JSON.stringify( - options.firmwareVersions, - ) - }"`, - ZWaveErrorCodes.Argument_Invalid, + super(options); + + if (!/^\d+\.\d+(\.\d+)?$/.test(options.protocolVersion)) { + throw new ZWaveError( + `protocolVersion must be a string in the format "major.minor" or "major.minor.patch", received "${options.protocolVersion}"`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if ( + !options.firmwareVersions.every((fw) => + /^\d+\.\d+(\.\d+)?$/.test(fw) + ) + ) { + throw new ZWaveError( + `firmwareVersions must be an array of strings in the format "major.minor" or "major.minor.patch", received "${ + JSON.stringify( + options.firmwareVersions, + ) + }"`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.libraryType = options.libraryType; + this.protocolVersion = options.protocolVersion; + this.firmwareVersions = options.firmwareVersions; + this.hardwareVersion = options.hardwareVersion; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): VersionCCReport { + validatePayload(raw.payload.length >= 5); + const libraryType: ZWaveLibraryTypes = raw.payload[0]; + const protocolVersion = `${raw.payload[1]}.${raw.payload[2]}`; + const firmwareVersions = [`${raw.payload[3]}.${raw.payload[4]}`]; + + let hardwareVersion: number | undefined; + if (raw.payload.length >= 7) { + // V2+ + hardwareVersion = raw.payload[5]; + const additionalFirmwares = raw.payload[6]; + validatePayload( + raw.payload.length >= 7 + 2 * additionalFirmwares, + ); + for (let i = 0; i < additionalFirmwares; i++) { + firmwareVersions.push( + `${raw.payload[7 + 2 * i]}.${raw.payload[7 + 2 * i + 1]}`, ); } - this.libraryType = options.libraryType; - this.protocolVersion = options.protocolVersion; - this.firmwareVersions = options.firmwareVersions; - this.hardwareVersion = options.hardwareVersion; } + + return new VersionCCReport({ + nodeId: ctx.sourceNodeId, + libraryType, + protocolVersion, + firmwareVersions, + hardwareVersion, + }); } @ccValue(VersionCCValues.libraryType) @@ -727,7 +722,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 +751,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 +767,7 @@ export class VersionCCReport extends VersionCC { message["hardware version"] = this.hardwareVersion; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -783,7 +778,7 @@ export class VersionCCReport extends VersionCC { export class VersionCCGet extends VersionCC {} // @publicAPI -export interface VersionCCCommandClassReportOptions extends CCCommandOptions { +export interface VersionCCCommandClassReportOptions { requestedCC: CommandClasses; ccVersion: number; } @@ -791,33 +786,39 @@ export interface VersionCCCommandClassReportOptions extends CCCommandOptions { @CCCommand(VersionCommand.CommandClassReport) export class VersionCCCommandClassReport extends VersionCC { public constructor( - host: ZWaveHost, - options: - | VersionCCCommandClassReportOptions - | CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.requestedCC = this.payload[0]; - this.ccVersion = this.payload[1]; - } else { - this.requestedCC = options.requestedCC; - this.ccVersion = options.ccVersion; - } + super(options); + this.requestedCC = options.requestedCC; + this.ccVersion = options.ccVersion; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): VersionCCCommandClassReport { + validatePayload(raw.payload.length >= 2); + const requestedCC: CommandClasses = raw.payload[0]; + const ccVersion = raw.payload[1]; + + return new VersionCCCommandClassReport({ + nodeId: ctx.sourceNodeId, + requestedCC, + ccVersion, + }); } 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, @@ -827,7 +828,7 @@ export class VersionCCCommandClassReport extends VersionCC { } // @publicAPI -export interface VersionCCCommandClassGetOptions extends CCCommandOptions { +export interface VersionCCCommandClassGetOptions { requestedCC: CommandClasses; } @@ -846,30 +847,35 @@ function testResponseForVersionCommandClassGet( ) export class VersionCCCommandClassGet extends VersionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | VersionCCCommandClassGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.requestedCC = this.payload[0]; - } else { - this.requestedCC = options.requestedCC; - } + super(options); + this.requestedCC = options.requestedCC; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): VersionCCCommandClassGet { + validatePayload(raw.payload.length >= 1); + const requestedCC: CommandClasses = raw.payload[0]; + + return new VersionCCCommandClassGet({ + nodeId: ctx.sourceNodeId, + requestedCC, + }); } 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,35 +889,40 @@ export interface VersionCCCapabilitiesReportOptions { @CCCommand(VersionCommand.CapabilitiesReport) export class VersionCCCapabilitiesReport extends VersionCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (VersionCCCapabilitiesReportOptions & CCCommandOptions), + options: WithAddress, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const capabilities = this.payload[0]; - this.supportsZWaveSoftwareGet = !!(capabilities & 0b100); - } else { - this.supportsZWaveSoftwareGet = options.supportsZWaveSoftwareGet; - } + this.supportsZWaveSoftwareGet = options.supportsZWaveSoftwareGet; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): VersionCCCapabilitiesReport { + validatePayload(raw.payload.length >= 1); + const capabilities = raw.payload[0]; + const supportsZWaveSoftwareGet = !!(capabilities & 0b100); + + return new VersionCCCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + supportsZWaveSoftwareGet, + }); } @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, @@ -924,42 +935,92 @@ export class VersionCCCapabilitiesReport extends VersionCC { @expectedCCResponse(VersionCCCapabilitiesReport) export class VersionCCCapabilitiesGet extends VersionCC {} +// @publicAPI +export interface VersionCCZWaveSoftwareReportOptions { + sdkVersion: string; + applicationFrameworkAPIVersion: string; + applicationFrameworkBuildNumber: number; + hostInterfaceVersion: string; + hostInterfaceBuildNumber: number; + zWaveProtocolVersion: string; + zWaveProtocolBuildNumber: number; + applicationVersion: string; + applicationBuildNumber: number; +} + @CCCommand(VersionCommand.ZWaveSoftwareReport) export class VersionCCZWaveSoftwareReport extends VersionCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.sdkVersion = options.sdkVersion; + this.applicationFrameworkAPIVersion = + options.applicationFrameworkAPIVersion; + this.applicationFrameworkBuildNumber = + options.applicationFrameworkBuildNumber; + this.hostInterfaceVersion = options.hostInterfaceVersion; + this.hostInterfaceBuildNumber = options.hostInterfaceBuildNumber; + this.zWaveProtocolVersion = options.zWaveProtocolVersion; + this.zWaveProtocolBuildNumber = options.zWaveProtocolBuildNumber; + this.applicationVersion = options.applicationVersion; + this.applicationBuildNumber = options.applicationBuildNumber; + } - validatePayload(this.payload.length >= 23); - this.sdkVersion = parseVersion(this.payload); - this.applicationFrameworkAPIVersion = parseVersion( - this.payload.subarray(3), + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): VersionCCZWaveSoftwareReport { + validatePayload(raw.payload.length >= 23); + const sdkVersion = parseVersion(raw.payload); + const applicationFrameworkAPIVersion = parseVersion( + raw.payload.subarray(3), ); - if (this.applicationFrameworkAPIVersion !== "unused") { - this.applicationFrameworkBuildNumber = this.payload.readUInt16BE(6); + let applicationFrameworkBuildNumber; + if (applicationFrameworkAPIVersion !== "unused") { + applicationFrameworkBuildNumber = raw.payload.readUInt16BE(6); } else { - this.applicationFrameworkBuildNumber = 0; + applicationFrameworkBuildNumber = 0; } - this.hostInterfaceVersion = parseVersion(this.payload.subarray(8)); - if (this.hostInterfaceVersion !== "unused") { - this.hostInterfaceBuildNumber = this.payload.readUInt16BE(11); + + const hostInterfaceVersion = parseVersion(raw.payload.subarray(8)); + let hostInterfaceBuildNumber; + if (hostInterfaceVersion !== "unused") { + hostInterfaceBuildNumber = raw.payload.readUInt16BE(11); } else { - this.hostInterfaceBuildNumber = 0; + hostInterfaceBuildNumber = 0; } - this.zWaveProtocolVersion = parseVersion(this.payload.subarray(13)); - if (this.zWaveProtocolVersion !== "unused") { - this.zWaveProtocolBuildNumber = this.payload.readUInt16BE(16); + + const zWaveProtocolVersion = parseVersion(raw.payload.subarray(13)); + let zWaveProtocolBuildNumber; + if (zWaveProtocolVersion !== "unused") { + zWaveProtocolBuildNumber = raw.payload.readUInt16BE(16); } else { - this.zWaveProtocolBuildNumber = 0; + zWaveProtocolBuildNumber = 0; } - this.applicationVersion = parseVersion(this.payload.subarray(18)); - if (this.applicationVersion !== "unused") { - this.applicationBuildNumber = this.payload.readUInt16BE(21); + + const applicationVersion = parseVersion(raw.payload.subarray(18)); + let applicationBuildNumber; + if (applicationVersion !== "unused") { + applicationBuildNumber = raw.payload.readUInt16BE(21); } else { - this.applicationBuildNumber = 0; + applicationBuildNumber = 0; } + + return new VersionCCZWaveSoftwareReport({ + nodeId: ctx.sourceNodeId, + sdkVersion, + applicationFrameworkAPIVersion, + applicationFrameworkBuildNumber, + hostInterfaceVersion, + hostInterfaceBuildNumber, + zWaveProtocolVersion, + zWaveProtocolBuildNumber, + applicationVersion, + applicationBuildNumber, + }); } @ccValue(VersionCCValues.sdkVersion) @@ -989,7 +1050,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 +1075,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..d209b426df5a 100644 --- a/packages/cc/src/cc/WakeUpCC.ts +++ b/packages/cc/src/cc/WakeUpCC.ts @@ -6,13 +6,14 @@ import { type SupervisionResult, TransmitOptions, ValueMetadata, + type WithAddress, supervisedCommandSucceeded, 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,10 +28,10 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, + type PersistValuesContext, } from "../lib/CommandClass"; import { API, @@ -107,7 +108,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 +135,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WakeUpCCIntervalReport >( cc, @@ -156,11 +157,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WakeUpCCIntervalCapabilitiesReport >( cc, @@ -184,13 +185,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, + endpointIndex: 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 +200,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, + endpointIndex: 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 +223,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 +259,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 +274,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 +288,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 +299,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 +313,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 +330,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,12 +349,12 @@ controller node: ${wakeupResp.controllerNodeId}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } // @publicAPI -export interface WakeUpCCIntervalSetOptions extends CCCommandOptions { +export interface WakeUpCCIntervalSetOptions { wakeUpInterval: number; controllerNodeId: number; } @@ -360,26 +363,29 @@ export interface WakeUpCCIntervalSetOptions extends CCCommandOptions { @useSupervision() export class WakeUpCCIntervalSet extends WakeUpCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WakeUpCCIntervalSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.wakeUpInterval = this.payload.readUIntBE(0, 3); - this.controllerNodeId = this.payload[3]; - } else { - this.wakeUpInterval = options.wakeUpInterval; - this.controllerNodeId = options.controllerNodeId; - } + super(options); + this.wakeUpInterval = options.wakeUpInterval; + this.controllerNodeId = options.controllerNodeId; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): WakeUpCCIntervalSet { + validatePayload(raw.payload.length >= 4); + const wakeUpInterval = raw.payload.readUIntBE(0, 3); + const controllerNodeId = raw.payload[3]; + + return new WakeUpCCIntervalSet({ + nodeId: ctx.sourceNodeId, + wakeUpInterval, + controllerNodeId, + }); } public wakeUpInterval: number; public controllerNodeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([ 0, 0, @@ -387,12 +393,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, @@ -401,17 +407,37 @@ export class WakeUpCCIntervalSet extends WakeUpCC { } } +// @publicAPI +export interface WakeUpCCIntervalReportOptions { + wakeUpInterval: number; + controllerNodeId: number; +} + @CCCommand(WakeUpCommand.IntervalReport) export class WakeUpCCIntervalReport extends WakeUpCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 4); - this.wakeUpInterval = this.payload.readUIntBE(0, 3); - this.controllerNodeId = this.payload[3]; + // TODO: Check implementation: + this.wakeUpInterval = options.wakeUpInterval; + this.controllerNodeId = options.controllerNodeId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WakeUpCCIntervalReport { + validatePayload(raw.payload.length >= 4); + const wakeUpInterval = raw.payload.readUIntBE(0, 3); + const controllerNodeId = raw.payload[3]; + + return new WakeUpCCIntervalReport({ + nodeId: ctx.sourceNodeId, + wakeUpInterval, + controllerNodeId, + }); } @ccValue(WakeUpCCValues.wakeUpInterval) @@ -420,9 +446,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, @@ -441,31 +467,58 @@ export class WakeUpCCWakeUpNotification extends WakeUpCC {} @CCCommand(WakeUpCommand.NoMoreInformation) export class WakeUpCCNoMoreInformation extends WakeUpCC {} +// @publicAPI +export interface WakeUpCCIntervalCapabilitiesReportOptions { + minWakeUpInterval: number; + maxWakeUpInterval: number; + defaultWakeUpInterval: number; + wakeUpIntervalSteps: number; + wakeUpOnDemandSupported: boolean; +} + @CCCommand(WakeUpCommand.IntervalCapabilitiesReport) export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - - validatePayload(this.payload.length >= 12); - this.minWakeUpInterval = this.payload.readUIntBE(0, 3); - this.maxWakeUpInterval = this.payload.readUIntBE(3, 3); - this.defaultWakeUpInterval = this.payload.readUIntBE(6, 3); - this.wakeUpIntervalSteps = this.payload.readUIntBE(9, 3); + super(options); + + // TODO: Check implementation: + this.minWakeUpInterval = options.minWakeUpInterval; + this.maxWakeUpInterval = options.maxWakeUpInterval; + this.defaultWakeUpInterval = options.defaultWakeUpInterval; + this.wakeUpIntervalSteps = options.wakeUpIntervalSteps; + this.wakeUpOnDemandSupported = options.wakeUpOnDemandSupported; + } - // Get 'Wake Up on Demand Support' if node supports V3 and sends 13th byte - if (this.version >= 3 && this.payload.length >= 13) { - this.wakeUpOnDemandSupported = !!(this.payload[12] & 0b1); - } else { - this.wakeUpOnDemandSupported = false; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WakeUpCCIntervalCapabilitiesReport { + validatePayload(raw.payload.length >= 12); + const minWakeUpInterval = raw.payload.readUIntBE(0, 3); + const maxWakeUpInterval = raw.payload.readUIntBE(3, 3); + const defaultWakeUpInterval = raw.payload.readUIntBE(6, 3); + const wakeUpIntervalSteps = raw.payload.readUIntBE(9, 3); + let wakeUpOnDemandSupported = false; + if (raw.payload.length >= 13) { + // V3+ + wakeUpOnDemandSupported = !!(raw.payload[12] & 0b1); } + + return new WakeUpCCIntervalCapabilitiesReport({ + nodeId: ctx.sourceNodeId, + minWakeUpInterval, + maxWakeUpInterval, + defaultWakeUpInterval, + wakeUpIntervalSteps, + wakeUpOnDemandSupported, + }); } - 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 +549,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..eb5e026b001c 100644 --- a/packages/cc/src/cc/WindowCoveringCC.ts +++ b/packages/cc/src/cc/WindowCoveringCC.ts @@ -6,15 +6,16 @@ import { type MessageRecord, type SupervisionResult, ValueMetadata, + type WithAddress, encodeBitMask, parseBitMask, validatePayload, } from "@zwave-js/core"; import { type MaybeNotKnown } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetValueDB, } from "@zwave-js/host"; import { getEnumMemberName, pick } from "@zwave-js/shared/safe"; import { validateArgs } from "@zwave-js/transformers"; @@ -32,10 +33,9 @@ import { throwWrongValueType, } from "../lib/API"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -418,7 +418,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 +473,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WindowCoveringCCSupportedReport >( cc, @@ -494,12 +494,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, + endpointIndex: this.endpoint.index, parameter, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< WindowCoveringCCReport >( cc, @@ -523,14 +523,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, + endpointIndex: this.endpoint.index, targetValues, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -544,15 +544,15 @@ 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, + endpointIndex: this.endpoint.index, parameter, direction, duration, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } @validateArgs({ strictEnums: true }) @@ -564,13 +564,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, + endpointIndex: this.endpoint.index, parameter, }); - return this.applHost.sendCommand(cc, this.commandOptions); + return this.host.sendCommand(cc, this.commandOptions); } } @@ -580,24 +580,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 +614,7 @@ ${ ) .join("\n") }`; - applHost.controllerLog.logNode(node.id, { + ctx.logNode(node.id, { endpoint: this.endpointIndex, message: logMessage, direction: "inbound", @@ -622,31 +624,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,57 +664,59 @@ ${ } // 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); } } // @publicAPI -export interface WindowCoveringCCSupportedReportOptions - extends CCCommandOptions -{ +export interface WindowCoveringCCSupportedReportOptions { supportedParameters: readonly WindowCoveringParameter[]; } @CCCommand(WindowCoveringCommand.SupportedReport) export class WindowCoveringCCSupportedReport extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WindowCoveringCCSupportedReportOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); + super(options); + this.supportedParameters = options.supportedParameters; + } - const numBitmaskBytes = this.payload[0] & 0b1111; - validatePayload(this.payload.length >= 1 + numBitmaskBytes); - const bitmask = this.payload.subarray(1, 1 + numBitmaskBytes); + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WindowCoveringCCSupportedReport { + validatePayload(raw.payload.length >= 1); + + const numBitmaskBytes = raw.payload[0] & 0b1111; + validatePayload(raw.payload.length >= 1 + numBitmaskBytes); + const bitmask = raw.payload.subarray(1, 1 + numBitmaskBytes); + const supportedParameters: WindowCoveringParameter[] = parseBitMask( + bitmask, + WindowCoveringParameter["Outbound Left (no position)"], + ); - this.supportedParameters = parseBitMask( - bitmask, - WindowCoveringParameter["Outbound Left (no position)"], - ); - } else { - this.supportedParameters = options.supportedParameters; - } + return new WindowCoveringCCSupportedReport({ + nodeId: ctx.sourceNodeId, + supportedParameters, + }); } @ccValue(WindowCoveringCCValues.supportedParameters) public readonly supportedParameters: readonly WindowCoveringParameter[]; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { const bitmask = encodeBitMask( this.supportedParameters, undefined, @@ -725,12 +729,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( @@ -752,19 +756,46 @@ export class WindowCoveringCCSupportedReport extends WindowCoveringCC { @expectedCCResponse(WindowCoveringCCSupportedReport) export class WindowCoveringCCSupportedGet extends WindowCoveringCC {} +// @publicAPI +export interface WindowCoveringCCReportOptions { + parameter: WindowCoveringParameter; + currentValue: number; + targetValue: number; + duration: Duration; +} + @CCCommand(WindowCoveringCommand.Report) export class WindowCoveringCCReport extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); - validatePayload(this.payload.length >= 4); - this.parameter = this.payload[0]; - this.currentValue = this.payload[1]; - this.targetValue = this.payload[2]; - this.duration = Duration.parseReport(this.payload[3]) + super(options); + + // TODO: Check implementation: + this.parameter = options.parameter; + this.currentValue = options.currentValue; + this.targetValue = options.targetValue; + this.duration = options.duration; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WindowCoveringCCReport { + validatePayload(raw.payload.length >= 4); + const parameter: WindowCoveringParameter = raw.payload[0]; + const currentValue = raw.payload[1]; + const targetValue = raw.payload[2]; + const duration = Duration.parseReport(raw.payload[3]) ?? Duration.unknown(); + + return new WindowCoveringCCReport({ + nodeId: ctx.sourceNodeId, + parameter, + currentValue, + targetValue, + duration, + }); } public readonly parameter: WindowCoveringParameter; @@ -785,9 +816,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, @@ -802,7 +833,7 @@ export class WindowCoveringCCReport extends WindowCoveringCC { } // @publicAPI -export interface WindowCoveringCCGetOptions extends CCCommandOptions { +export interface WindowCoveringCCGetOptions { parameter: WindowCoveringParameter; } @@ -817,30 +848,32 @@ function testResponseForWindowCoveringGet( @expectedCCResponse(WindowCoveringCCReport, testResponseForWindowCoveringGet) export class WindowCoveringCCGet extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WindowCoveringCCGetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.parameter = this.payload[0]; - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCGet { + validatePayload(raw.payload.length >= 1); + const parameter: WindowCoveringParameter = raw.payload[0]; + + return new WindowCoveringCCGet({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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, @@ -852,7 +885,7 @@ export class WindowCoveringCCGet extends WindowCoveringCC { } // @publicAPI -export interface WindowCoveringCCSetOptions extends CCCommandOptions { +export interface WindowCoveringCCSetOptions { targetValues: { parameter: WindowCoveringParameter; value: number; @@ -864,34 +897,41 @@ export interface WindowCoveringCCSetOptions extends CCCommandOptions { @useSupervision() export class WindowCoveringCCSet extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WindowCoveringCCSetOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const numEntries = this.payload[0] & 0b11111; - - validatePayload(this.payload.length >= 1 + numEntries * 2); - this.targetValues = []; - for (let i = 0; i < numEntries; i++) { - const offset = 1 + i * 2; - this.targetValues.push({ - parameter: this.payload[offset], - value: this.payload[offset + 1], - }); - } - if (this.payload.length >= 2 + numEntries * 2) { - this.duration = Duration.parseSet( - this.payload[1 + numEntries * 2], - ); - } - } else { - this.targetValues = options.targetValues; - this.duration = Duration.from(options.duration); + super(options); + this.targetValues = options.targetValues; + this.duration = Duration.from(options.duration); + } + + public static from(raw: CCRaw, ctx: CCParsingContext): WindowCoveringCCSet { + validatePayload(raw.payload.length >= 1); + const numEntries = raw.payload[0] & 0b11111; + + validatePayload(raw.payload.length >= 1 + numEntries * 2); + const targetValues: WindowCoveringCCSetOptions["targetValues"] = []; + + for (let i = 0; i < numEntries; i++) { + const offset = 1 + i * 2; + targetValues.push({ + parameter: raw.payload[offset], + value: raw.payload[offset + 1], + }); + } + + let duration: Duration | undefined; + + if (raw.payload.length >= 2 + numEntries * 2) { + duration = Duration.parseSet( + raw.payload[1 + numEntries * 2], + ); } + + return new WindowCoveringCCSet({ + nodeId: ctx.sourceNodeId, + targetValues, + duration, + }); } public targetValues: { @@ -900,7 +940,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 +955,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,16 +968,14 @@ export class WindowCoveringCCSet extends WindowCoveringCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface WindowCoveringCCStartLevelChangeOptions - extends CCCommandOptions -{ +export interface WindowCoveringCCStartLevelChangeOptions { parameter: WindowCoveringParameter; direction: keyof typeof LevelChangeDirection; duration?: Duration | string; @@ -947,40 +985,51 @@ export interface WindowCoveringCCStartLevelChangeOptions @useSupervision() export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WindowCoveringCCStartLevelChangeOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.direction = !!(this.payload[0] & 0b0100_0000) ? "down" : "up"; - this.parameter = this.payload[1]; - if (this.payload.length >= 3) { - this.duration = Duration.parseSet(this.payload[2]); - } - } else { - this.parameter = options.parameter; - this.direction = options.direction; - this.duration = Duration.from(options.duration); + super(options); + this.parameter = options.parameter; + this.direction = options.direction; + this.duration = Duration.from(options.duration); + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WindowCoveringCCStartLevelChange { + validatePayload(raw.payload.length >= 2); + const direction = !!(raw.payload[0] & 0b0100_0000) + ? "down" + : "up"; + const parameter: WindowCoveringParameter = raw.payload[1]; + let duration: Duration | undefined; + + if (raw.payload.length >= 3) { + duration = Duration.parseSet(raw.payload[2]); } + + return new WindowCoveringCCStartLevelChange({ + nodeId: ctx.sourceNodeId, + direction, + parameter, + duration, + }); } public parameter: WindowCoveringParameter; 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,16 +1041,14 @@ export class WindowCoveringCCStartLevelChange extends WindowCoveringCC { message.duration = this.duration.toString(); } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } // @publicAPI -export interface WindowCoveringCCStopLevelChangeOptions - extends CCCommandOptions -{ +export interface WindowCoveringCCStopLevelChangeOptions { parameter: WindowCoveringParameter; } @@ -1009,30 +1056,35 @@ export interface WindowCoveringCCStopLevelChangeOptions @useSupervision() export class WindowCoveringCCStopLevelChange extends WindowCoveringCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | WindowCoveringCCStopLevelChangeOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.parameter = this.payload[0]; - } else { - this.parameter = options.parameter; - } + super(options); + this.parameter = options.parameter; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): WindowCoveringCCStopLevelChange { + validatePayload(raw.payload.length >= 1); + const parameter: WindowCoveringParameter = raw.payload[0]; + + return new WindowCoveringCCStopLevelChange({ + nodeId: ctx.sourceNodeId, + parameter, + }); } 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..d8bf67d13daf 100644 --- a/packages/cc/src/cc/ZWavePlusCC.ts +++ b/packages/cc/src/cc/ZWavePlusCC.ts @@ -3,21 +3,21 @@ import { type MaybeNotKnown, type MessageOrCCLogEntry, MessagePriority, + type WithAddress, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + 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"; import { - type CCCommandOptions, + type CCRaw, CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type InterviewContext, } from "../lib/CommandClass"; import { API, @@ -83,11 +83,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand( + const response = await this.host.sendCommand( cc, this.commandOptions, ); @@ -106,12 +106,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, + endpointIndex: this.endpoint.index, ...options, }); - await this.applHost.sendCommand(cc, this.commandOptions); + await this.host.sendCommand(cc, this.commandOptions); } } @@ -121,24 +121,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 +154,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 +162,7 @@ user icon: ${num2hex(zwavePlusResponse.userIcon)}`; } // Remember that the interview is complete - this.setInterviewComplete(applHost, true); + this.setInterviewComplete(ctx, true); } } @@ -176,26 +178,32 @@ export interface ZWavePlusCCReportOptions { @CCCommand(ZWavePlusCommand.Report) export class ZWavePlusCCReport extends ZWavePlusCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | (CCCommandOptions & ZWavePlusCCReportOptions), + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 7); - this.zwavePlusVersion = this.payload[0]; - this.roleType = this.payload[1]; - this.nodeType = this.payload[2]; - this.installerIcon = this.payload.readUInt16BE(3); - this.userIcon = this.payload.readUInt16BE(5); - } else { - this.zwavePlusVersion = options.zwavePlusVersion; - this.roleType = options.roleType; - this.nodeType = options.nodeType; - this.installerIcon = options.installerIcon; - this.userIcon = options.userIcon; - } + super(options); + this.zwavePlusVersion = options.zwavePlusVersion; + this.roleType = options.roleType; + this.nodeType = options.nodeType; + this.installerIcon = options.installerIcon; + this.userIcon = options.userIcon; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ZWavePlusCCReport { + validatePayload(raw.payload.length >= 7); + const zwavePlusVersion = raw.payload[0]; + const roleType: ZWavePlusRoleType = raw.payload[1]; + const nodeType: ZWavePlusNodeType = raw.payload[2]; + const installerIcon = raw.payload.readUInt16BE(3); + const userIcon = raw.payload.readUInt16BE(5); + + return new ZWavePlusCCReport({ + nodeId: ctx.sourceNodeId, + zwavePlusVersion, + roleType, + nodeType, + installerIcon, + userIcon, + }); } @ccValue(ZWavePlusCCValues.zwavePlusVersion) @@ -213,7 +221,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 +234,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..f9b1d4a378a5 100644 --- a/packages/cc/src/cc/ZWaveProtocolCC.ts +++ b/packages/cc/src/cc/ZWaveProtocolCC.ts @@ -9,6 +9,7 @@ import { type NodeProtocolInfoAndDeviceClass, type NodeType, type ProtocolVersion, + type WithAddress, ZWaveDataRate, ZWaveError, ZWaveErrorCodes, @@ -20,13 +21,8 @@ import { parseNodeProtocolInfoAndDeviceClass, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - type CCCommandOptions, - CommandClass, - type CommandClassDeserializationOptions, - gotDeserializationOptions, -} from "../lib/CommandClass"; +import type { CCEncodingContext, CCParsingContext } from "@zwave-js/host"; +import { type CCRaw, CommandClass } from "../lib/CommandClass"; import { CCCommand, commandClass, @@ -69,8 +65,9 @@ export class ZWaveProtocolCC extends CommandClass { } // @publicAPI +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ZWaveProtocolCCNodeInformationFrameOptions - extends CCCommandOptions, NodeInformationFrame + extends NodeInformationFrame {} @CCCommand(ZWaveProtocolCommand.NodeInformationFrame) @@ -78,33 +75,35 @@ export class ZWaveProtocolCCNodeInformationFrame extends ZWaveProtocolCC implements NodeInformationFrame { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNodeInformationFrameOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + this.basicDeviceClass = options.basicDeviceClass; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.isListening = options.isListening; + this.isFrequentListening = options.isFrequentListening; + this.isRouting = options.isRouting; + this.supportedDataRates = options.supportedDataRates; + this.protocolVersion = options.protocolVersion; + this.optionalFunctionality = options.optionalFunctionality; + this.nodeType = options.nodeType; + this.supportsSecurity = options.supportsSecurity; + this.supportsBeaming = options.supportsBeaming; + this.supportedCCs = options.supportedCCs; + } - let nif: NodeInformationFrame; - if (gotDeserializationOptions(options)) { - nif = parseNodeInformationFrame(this.payload); - } else { - nif = options; - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNodeInformationFrame { + const nif = parseNodeInformationFrame(raw.payload); - this.basicDeviceClass = nif.basicDeviceClass; - this.genericDeviceClass = nif.genericDeviceClass; - this.specificDeviceClass = nif.specificDeviceClass; - this.isListening = nif.isListening; - this.isFrequentListening = nif.isFrequentListening; - this.isRouting = nif.isRouting; - this.supportedDataRates = nif.supportedDataRates; - this.protocolVersion = nif.protocolVersion; - this.optionalFunctionality = nif.optionalFunctionality; - this.nodeType = nif.nodeType; - this.supportsSecurity = nif.supportsSecurity; - this.supportsBeaming = nif.supportsBeaming; - this.supportedCCs = nif.supportedCCs; + return new ZWaveProtocolCCNodeInformationFrame({ + nodeId: ctx.sourceNodeId, + ...nif, + }); } public basicDeviceClass: BasicDeviceClass; @@ -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); } } @@ -134,7 +133,7 @@ export class ZWaveProtocolCCRequestNodeInformationFrame {} // @publicAPI -export interface ZWaveProtocolCCAssignIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAssignIDsOptions { assignedNodeId: number; homeId: number; } @@ -142,37 +141,41 @@ export interface ZWaveProtocolCCAssignIDsOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.AssignIDs) export class ZWaveProtocolCCAssignIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCAssignIDsOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 5); - this.assignedNodeId = this.payload[0]; - this.homeId = this.payload.readUInt32BE(1); - } else { - this.assignedNodeId = options.assignedNodeId; - this.homeId = options.homeId; - } + super(options); + this.assignedNodeId = options.assignedNodeId; + this.homeId = options.homeId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCAssignIDs { + validatePayload(raw.payload.length >= 5); + const assignedNodeId = raw.payload[0]; + const homeId = raw.payload.readUInt32BE(1); + + return new ZWaveProtocolCCAssignIDs({ + nodeId: ctx.sourceNodeId, + assignedNodeId, + homeId, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCFindNodesInRangeOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCFindNodesInRangeOptions { candidateNodeIds: number[]; wakeUpTime: WakeUpTime; dataRate?: ZWaveDataRate; @@ -181,53 +184,62 @@ export interface ZWaveProtocolCCFindNodesInRangeOptions @CCCommand(ZWaveProtocolCommand.FindNodesInRange) export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCFindNodesInRangeOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const speedPresent = this.payload[0] & 0b1000_0000; - const bitmaskLength = this.payload[0] & 0b0001_1111; - - validatePayload(this.payload.length >= 1 + bitmaskLength); - this.candidateNodeIds = parseBitMask( - this.payload.subarray(1, 1 + bitmaskLength), - ); + super(options); + this.candidateNodeIds = options.candidateNodeIds; + this.wakeUpTime = options.wakeUpTime; + this.dataRate = options.dataRate ?? ZWaveDataRate["9k6"]; + } - const rest = this.payload.subarray(1 + bitmaskLength); - if (speedPresent) { - validatePayload(rest.length >= 1); - if (rest.length === 1) { - this.dataRate = rest[0] & 0b111; - this.wakeUpTime = WakeUpTime.None; - } else if (rest.length === 2) { - this.wakeUpTime = parseWakeUpTime(rest[0]); - this.dataRate = rest[1] & 0b111; - } else { - validatePayload.fail("Invalid payload length"); - } - } else if (rest.length >= 1) { - this.wakeUpTime = parseWakeUpTime(rest[0]); - this.dataRate = ZWaveDataRate["9k6"]; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCFindNodesInRange { + validatePayload(raw.payload.length >= 1); + const speedPresent = raw.payload[0] & 0b1000_0000; + const bitmaskLength = raw.payload[0] & 0b0001_1111; + + validatePayload(raw.payload.length >= 1 + bitmaskLength); + const candidateNodeIds = parseBitMask( + raw.payload.subarray(1, 1 + bitmaskLength), + ); + const rest = raw.payload.subarray(1 + bitmaskLength); + + let dataRate: ZWaveDataRate; + let wakeUpTime: WakeUpTime; + if (speedPresent) { + validatePayload(rest.length >= 1); + if (rest.length === 1) { + dataRate = rest[0] & 0b111; + wakeUpTime = WakeUpTime.None; + } else if (rest.length === 2) { + wakeUpTime = parseWakeUpTime(rest[0]); + dataRate = rest[1] & 0b111; } else { - this.wakeUpTime = WakeUpTime.None; - this.dataRate = ZWaveDataRate["9k6"]; + validatePayload.fail("Invalid payload length"); } + } else if (rest.length >= 1) { + wakeUpTime = parseWakeUpTime(rest[0]); + dataRate = ZWaveDataRate["9k6"]; } else { - this.candidateNodeIds = options.candidateNodeIds; - this.wakeUpTime = options.wakeUpTime; - this.dataRate = options.dataRate ?? ZWaveDataRate["9k6"]; + wakeUpTime = WakeUpTime.None; + dataRate = ZWaveDataRate["9k6"]; } + + return new ZWaveProtocolCCFindNodesInRange({ + nodeId: ctx.sourceNodeId, + candidateNodeIds, + dataRate, + wakeUpTime, + }); } public candidateNodeIds: number[]; 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,12 +247,12 @@ export class ZWaveProtocolCCFindNodesInRange extends ZWaveProtocolCC { nodesBitmask, Buffer.from([this.wakeUpTime, this.dataRate]), ]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCRangeInfoOptions extends CCCommandOptions { +export interface ZWaveProtocolCCRangeInfoOptions { neighborNodeIds: number[]; wakeUpTime?: WakeUpTime; } @@ -248,35 +260,43 @@ export interface ZWaveProtocolCCRangeInfoOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.RangeInfo) export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCRangeInfoOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const bitmaskLength = this.payload[0] & 0b0001_1111; - - validatePayload(this.payload.length >= 1 + bitmaskLength); - this.neighborNodeIds = parseBitMask( - this.payload.subarray(1, 1 + bitmaskLength), + super(options); + this.neighborNodeIds = options.neighborNodeIds; + this.wakeUpTime = options.wakeUpTime; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCRangeInfo { + validatePayload(raw.payload.length >= 1); + const bitmaskLength = raw.payload[0] & 0b0001_1111; + + validatePayload(raw.payload.length >= 1 + bitmaskLength); + const neighborNodeIds = parseBitMask( + raw.payload.subarray(1, 1 + bitmaskLength), + ); + + let wakeUpTime: WakeUpTime | undefined; + if (raw.payload.length >= 2 + bitmaskLength) { + wakeUpTime = parseWakeUpTime( + raw.payload[1 + bitmaskLength], ); - if (this.payload.length >= 2 + bitmaskLength) { - this.wakeUpTime = parseWakeUpTime( - this.payload[1 + bitmaskLength], - ); - } - } else { - this.neighborNodeIds = options.neighborNodeIds; - this.wakeUpTime = options.wakeUpTime; } + + return new ZWaveProtocolCCRangeInfo({ + nodeId: ctx.sourceNodeId, + neighborNodeIds, + wakeUpTime, + }); } 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 +305,7 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { ? Buffer.from([this.wakeUpTime]) : Buffer.alloc(0), ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -294,41 +314,42 @@ export class ZWaveProtocolCCRangeInfo extends ZWaveProtocolCC { export class ZWaveProtocolCCGetNodesInRange extends ZWaveProtocolCC {} // @publicAPI -export interface ZWaveProtocolCCCommandCompleteOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCCommandCompleteOptions { sequenceNumber: number; } @CCCommand(ZWaveProtocolCommand.CommandComplete) export class ZWaveProtocolCCCommandComplete extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCCommandCompleteOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.sequenceNumber = this.payload[0]; - } else { - this.sequenceNumber = options.sequenceNumber; - } + super(options); + this.sequenceNumber = options.sequenceNumber; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCCommandComplete { + validatePayload(raw.payload.length >= 1); + const sequenceNumber = raw.payload[0]; + + return new ZWaveProtocolCCCommandComplete({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + }); } public sequenceNumber: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.sequenceNumber]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCTransferPresentationOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCTransferPresentationOptions { supportsNWI: boolean; includeNode: boolean; excludeNode: boolean; @@ -337,48 +358,55 @@ export interface ZWaveProtocolCCTransferPresentationOptions @CCCommand(ZWaveProtocolCommand.TransferPresentation) export class ZWaveProtocolCCTransferPresentation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCTransferPresentationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const option = this.payload[0]; - this.supportsNWI = !!(option & 0b0001); - this.excludeNode = !!(option & 0b0010); - this.includeNode = !!(option & 0b0100); - } else { - if (options.includeNode && options.excludeNode) { - throw new ZWaveError( - `${this.constructor.name}: the includeNode and excludeNode options cannot both be true`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.supportsNWI = options.supportsNWI; - this.includeNode = options.includeNode; - this.excludeNode = options.excludeNode; + super(options); + if (options.includeNode && options.excludeNode) { + throw new ZWaveError( + `${this.constructor.name}: the includeNode and excludeNode options cannot both be true`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.supportsNWI = options.supportsNWI; + this.includeNode = options.includeNode; + this.excludeNode = options.excludeNode; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCTransferPresentation { + validatePayload(raw.payload.length >= 1); + const option = raw.payload[0]; + const supportsNWI = !!(option & 0b0001); + const excludeNode = !!(option & 0b0010); + const includeNode = !!(option & 0b0100); + + return new ZWaveProtocolCCTransferPresentation({ + nodeId: ctx.sourceNodeId, + supportsNWI, + excludeNode, + includeNode, + }); } public supportsNWI: boolean; 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); } } // @publicAPI export interface ZWaveProtocolCCTransferNodeInformationOptions - extends CCCommandOptions, NodeProtocolInfoAndDeviceClass + extends NodeProtocolInfoAndDeviceClass { sequenceNumber: number; sourceNodeId: number; @@ -389,39 +417,45 @@ export class ZWaveProtocolCCTransferNodeInformation extends ZWaveProtocolCC implements NodeProtocolInfoAndDeviceClass { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCTransferNodeInformationOptions, + options: WithAddress, ) { - super(host, options); - - let info: NodeProtocolInfoAndDeviceClass; - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.sequenceNumber = this.payload[0]; - this.sourceNodeId = this.payload[1]; - info = parseNodeProtocolInfoAndDeviceClass( - this.payload.subarray(2), - ).info; - } else { - this.sequenceNumber = options.sequenceNumber; - this.sourceNodeId = options.sourceNodeId; - info = options; - } + super(options); + + this.sequenceNumber = options.sequenceNumber; + this.sourceNodeId = options.sourceNodeId; + + this.basicDeviceClass = options.basicDeviceClass; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.isListening = options.isListening; + this.isFrequentListening = options.isFrequentListening; + this.isRouting = options.isRouting; + this.supportedDataRates = options.supportedDataRates; + this.protocolVersion = options.protocolVersion; + this.optionalFunctionality = options.optionalFunctionality; + this.nodeType = options.nodeType; + this.supportsSecurity = options.supportsSecurity; + this.supportsBeaming = options.supportsBeaming; + } - this.basicDeviceClass = info.basicDeviceClass; - this.genericDeviceClass = info.genericDeviceClass; - this.specificDeviceClass = info.specificDeviceClass; - this.isListening = info.isListening; - this.isFrequentListening = info.isFrequentListening; - this.isRouting = info.isRouting; - this.supportedDataRates = info.supportedDataRates; - this.protocolVersion = info.protocolVersion; - this.optionalFunctionality = info.optionalFunctionality; - this.nodeType = info.nodeType; - this.supportsSecurity = info.supportsSecurity; - this.supportsBeaming = info.supportsBeaming; + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCTransferNodeInformation { + validatePayload(raw.payload.length >= 2); + const sequenceNumber = raw.payload[0]; + const sourceNodeId = raw.payload[1]; + + const { info } = parseNodeProtocolInfoAndDeviceClass( + raw.payload.subarray(2), + ); + + return new ZWaveProtocolCCTransferNodeInformation({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + sourceNodeId, + ...info, + }); } public sequenceNumber: number; @@ -439,19 +473,17 @@ 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); } } // @publicAPI -export interface ZWaveProtocolCCTransferRangeInformationOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCTransferRangeInformationOptions { sequenceNumber: number; testedNodeId: number; neighborNodeIds: number[]; @@ -460,33 +492,41 @@ export interface ZWaveProtocolCCTransferRangeInformationOptions @CCCommand(ZWaveProtocolCommand.TransferRangeInformation) export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCTransferRangeInformationOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 3); - this.sequenceNumber = this.payload[0]; - this.testedNodeId = this.payload[1]; - const bitmaskLength = this.payload[2]; - validatePayload(this.payload.length >= 3 + bitmaskLength); - this.neighborNodeIds = parseBitMask( - this.payload.subarray(3, 3 + bitmaskLength), - ); - } else { - this.sequenceNumber = options.sequenceNumber; - this.testedNodeId = options.testedNodeId; - this.neighborNodeIds = options.neighborNodeIds; - } + super(options); + this.sequenceNumber = options.sequenceNumber; + this.testedNodeId = options.testedNodeId; + this.neighborNodeIds = options.neighborNodeIds; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCTransferRangeInformation { + validatePayload(raw.payload.length >= 3); + const sequenceNumber = raw.payload[0]; + const testedNodeId = raw.payload[1]; + const bitmaskLength = raw.payload[2]; + + validatePayload(raw.payload.length >= 3 + bitmaskLength); + const neighborNodeIds = parseBitMask( + raw.payload.subarray(3, 3 + bitmaskLength), + ); + + return new ZWaveProtocolCCTransferRangeInformation({ + nodeId: ctx.sourceNodeId, + sequenceNumber, + testedNodeId, + neighborNodeIds, + }); } public sequenceNumber: number; 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,44 +536,47 @@ export class ZWaveProtocolCCTransferRangeInformation extends ZWaveProtocolCC { ]), nodesBitmask, ]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCTransferEndOptions extends CCCommandOptions { +export interface ZWaveProtocolCCTransferEndOptions { status: NetworkTransferStatus; } @CCCommand(ZWaveProtocolCommand.TransferEnd) export class ZWaveProtocolCCTransferEnd extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCTransferEndOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.status = this.payload[0]; - } else { - this.status = options.status; - } + super(options); + this.status = options.status; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCTransferEnd { + validatePayload(raw.payload.length >= 1); + const status: NetworkTransferStatus = raw.payload[0]; + + return new ZWaveProtocolCCTransferEnd({ + nodeId: ctx.sourceNodeId, + status, + }); } public status: NetworkTransferStatus; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.status]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCAssignReturnRouteOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCAssignReturnRouteOptions { destinationNodeId: number; routeIndex: number; repeaters: number[]; @@ -544,37 +587,46 @@ export interface ZWaveProtocolCCAssignReturnRouteOptions @CCCommand(ZWaveProtocolCommand.AssignReturnRoute) export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCAssignReturnRouteOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 7); - this.destinationNodeId = this.payload[0]; - this.routeIndex = this.payload[1] >>> 4; - const numRepeaters = this.payload[1] & 0b1111; - this.repeaters = [...this.payload.subarray(2, 2 + numRepeaters)]; - const speedAndWakeup = this.payload[2 + numRepeaters]; - this.destinationSpeed = bitmask2DataRate( - (speedAndWakeup >>> 3) & 0b111, + super(options); + if (options.repeaters.length > MAX_REPEATERS) { + throw new ZWaveError( + `${this.constructor.name}: too many repeaters`, + ZWaveErrorCodes.Argument_Invalid, ); - this.destinationWakeUp = (speedAndWakeup >>> 1) & 0b11; - } else { - if (options.repeaters.length > MAX_REPEATERS) { - throw new ZWaveError( - `${this.constructor.name}: too many repeaters`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.destinationNodeId = options.destinationNodeId; - this.routeIndex = options.routeIndex; - this.repeaters = options.repeaters; - this.destinationWakeUp = options.destinationWakeUp; - this.destinationSpeed = options.destinationSpeed; } + + this.destinationNodeId = options.destinationNodeId; + this.routeIndex = options.routeIndex; + this.repeaters = options.repeaters; + this.destinationWakeUp = options.destinationWakeUp; + this.destinationSpeed = options.destinationSpeed; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCAssignReturnRoute { + validatePayload(raw.payload.length >= 7); + const destinationNodeId = raw.payload[0]; + const routeIndex = raw.payload[1] >>> 4; + const numRepeaters = raw.payload[1] & 0b1111; + const repeaters = [...raw.payload.subarray(2, 2 + numRepeaters)]; + const speedAndWakeup = raw.payload[2 + numRepeaters]; + const destinationSpeed = bitmask2DataRate( + (speedAndWakeup >>> 3) & 0b111, + ); + const destinationWakeUp: WakeUpTime = (speedAndWakeup >>> 1) & 0b11; + + return new ZWaveProtocolCCAssignReturnRoute({ + nodeId: ctx.sourceNodeId, + destinationNodeId, + routeIndex, + repeaters, + destinationSpeed, + destinationWakeUp, + }); } public destinationNodeId: number; @@ -583,7 +635,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,13 +645,13 @@ export class ZWaveProtocolCCAssignReturnRoute extends ZWaveProtocolCC { ...this.repeaters, speedByte, ]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI export interface ZWaveProtocolCCNewNodeRegisteredOptions - extends CCCommandOptions, NodeInformationFrame + extends NodeInformationFrame { newNodeId: number; } @@ -609,36 +661,40 @@ export class ZWaveProtocolCCNewNodeRegistered extends ZWaveProtocolCC implements NodeInformationFrame { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNewNodeRegisteredOptions, + options: WithAddress, ) { - super(host, options); + super(options); + + this.newNodeId = options.newNodeId; + this.basicDeviceClass = options.basicDeviceClass; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.isListening = options.isListening; + this.isFrequentListening = options.isFrequentListening; + this.isRouting = options.isRouting; + this.supportedDataRates = options.supportedDataRates; + this.protocolVersion = options.protocolVersion; + this.optionalFunctionality = options.optionalFunctionality; + this.nodeType = options.nodeType; + this.supportsSecurity = options.supportsSecurity; + this.supportsBeaming = options.supportsBeaming; + this.supportedCCs = options.supportedCCs; + } - let nif: NodeInformationFrame; - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.newNodeId = this.payload[0]; - nif = parseNodeInformationFrame(this.payload.subarray(1)); - } else { - this.newNodeId = options.newNodeId; - nif = options; - } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNewNodeRegistered { + validatePayload(raw.payload.length >= 1); + const newNodeId = raw.payload[0]; - this.basicDeviceClass = nif.basicDeviceClass; - this.genericDeviceClass = nif.genericDeviceClass; - this.specificDeviceClass = nif.specificDeviceClass; - this.isListening = nif.isListening; - this.isFrequentListening = nif.isFrequentListening; - this.isRouting = nif.isRouting; - this.supportedDataRates = nif.supportedDataRates; - this.protocolVersion = nif.protocolVersion; - this.optionalFunctionality = nif.optionalFunctionality; - this.nodeType = nif.nodeType; - this.supportsSecurity = nif.supportsSecurity; - this.supportsBeaming = nif.supportsBeaming; - this.supportedCCs = nif.supportedCCs; + const nif = parseNodeInformationFrame(raw.payload.subarray(1)); + + return new ZWaveProtocolCCNewNodeRegistered({ + nodeId: ctx.sourceNodeId, + newNodeId, + ...nif, + }); } public newNodeId: number; @@ -656,19 +712,17 @@ 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); } } // @publicAPI -export interface ZWaveProtocolCCNewRangeRegisteredOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCNewRangeRegisteredOptions { testedNodeId: number; neighborNodeIds: number[]; } @@ -676,42 +730,46 @@ export interface ZWaveProtocolCCNewRangeRegisteredOptions @CCCommand(ZWaveProtocolCommand.NewRangeRegistered) export class ZWaveProtocolCCNewRangeRegistered extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNewRangeRegisteredOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.testedNodeId = this.payload[0]; - const numNeighbors = this.payload[1]; - this.neighborNodeIds = [ - ...this.payload.subarray(2, 2 + numNeighbors), - ]; - } else { - this.testedNodeId = options.testedNodeId; - this.neighborNodeIds = options.neighborNodeIds; - } + super(options); + this.testedNodeId = options.testedNodeId; + this.neighborNodeIds = options.neighborNodeIds; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNewRangeRegistered { + validatePayload(raw.payload.length >= 2); + const testedNodeId = raw.payload[0]; + const numNeighbors = raw.payload[1]; + const neighborNodeIds = [ + ...raw.payload.subarray(2, 2 + numNeighbors), + ]; + + return new ZWaveProtocolCCNewRangeRegistered({ + nodeId: ctx.sourceNodeId, + testedNodeId, + neighborNodeIds, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions { genericDeviceClass: number; } @@ -720,25 +778,32 @@ export class ZWaveProtocolCCTransferNewPrimaryControllerComplete extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions, + options: WithAddress< + ZWaveProtocolCCTransferNewPrimaryControllerCompleteOptions + >, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.genericDeviceClass = this.payload[0]; - } else { - this.genericDeviceClass = options.genericDeviceClass; - } + super(options); + this.genericDeviceClass = options.genericDeviceClass; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCTransferNewPrimaryControllerComplete { + validatePayload(raw.payload.length >= 1); + const genericDeviceClass = raw.payload[0]; + + return new ZWaveProtocolCCTransferNewPrimaryControllerComplete({ + nodeId: ctx.sourceNodeId, + genericDeviceClass, + }); } public genericDeviceClass: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.genericDeviceClass]); - return super.serialize(); + return super.serialize(ctx); } } @@ -748,7 +813,7 @@ export class ZWaveProtocolCCAutomaticControllerUpdateStart {} // @publicAPI -export interface ZWaveProtocolCCSUCNodeIDOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSUCNodeIDOptions { sucNodeId: number; isSIS: boolean; } @@ -756,66 +821,77 @@ export interface ZWaveProtocolCCSUCNodeIDOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SUCNodeID) export class ZWaveProtocolCCSUCNodeID extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCSUCNodeIDOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.sucNodeId = this.payload[0]; - const capabilities = this.payload[1] ?? 0; - this.isSIS = !!(capabilities & 0b1); - } else { - this.sucNodeId = options.sucNodeId; - this.isSIS = options.isSIS; - } + super(options); + this.sucNodeId = options.sucNodeId; + this.isSIS = options.isSIS; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCSUCNodeID { + validatePayload(raw.payload.length >= 1); + const sucNodeId = raw.payload[0]; + const capabilities = raw.payload[1] ?? 0; + const isSIS = !!(capabilities & 0b1); + + return new ZWaveProtocolCCSUCNodeID({ + nodeId: ctx.sourceNodeId, + sucNodeId, + isSIS, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCSetSUCOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetSUCOptions { enableSIS: boolean; } @CCCommand(ZWaveProtocolCommand.SetSUC) export class ZWaveProtocolCCSetSUC extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCSetSUCOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - // Byte 0 must be 0x01 or ignored - const capabilities = this.payload[1] ?? 0; - this.enableSIS = !!(capabilities & 0b1); - } else { - this.enableSIS = options.enableSIS; - } + super(options); + this.enableSIS = options.enableSIS; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCSetSUC { + validatePayload(raw.payload.length >= 2); + // Byte 0 must be 0x01 or ignored + const capabilities = raw.payload[1] ?? 0; + const enableSIS = !!(capabilities & 0b1); + + return new ZWaveProtocolCCSetSUC({ + nodeId: ctx.sourceNodeId, + enableSIS, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCSetSUCAckOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetSUCAckOptions { accepted: boolean; isSIS: boolean; } @@ -823,32 +899,38 @@ export interface ZWaveProtocolCCSetSUCAckOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SetSUCAck) export class ZWaveProtocolCCSetSUCAck extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCSetSUCAckOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.accepted = this.payload[0] === 0x01; - const capabilities = this.payload[1] ?? 0; - this.isSIS = !!(capabilities & 0b1); - } else { - this.accepted = options.accepted; - this.isSIS = options.isSIS; - } + super(options); + this.accepted = options.accepted; + this.isSIS = options.isSIS; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCSetSUCAck { + validatePayload(raw.payload.length >= 2); + const accepted = raw.payload[0] === 0x01; + const capabilities = raw.payload[1] ?? 0; + const isSIS = !!(capabilities & 0b1); + + return new ZWaveProtocolCCSetSUCAck({ + nodeId: ctx.sourceNodeId, + accepted, + isSIS, + }); } 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); } } @@ -858,210 +940,232 @@ export class ZWaveProtocolCCAssignSUCReturnRoute {} // @publicAPI -export interface ZWaveProtocolCCStaticRouteRequestOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCStaticRouteRequestOptions { nodeIds: number[]; } @CCCommand(ZWaveProtocolCommand.StaticRouteRequest) export class ZWaveProtocolCCStaticRouteRequest extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCStaticRouteRequestOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 5); - this.nodeIds = [...this.payload.subarray(0, 5)].filter( - (id) => id > 0 && id <= MAX_NODES, + super(options); + if (options.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { + throw new ZWaveError( + `All node IDs must be between 1 and ${MAX_NODES}!`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.nodeIds.some((n) => n < 1 || n > MAX_NODES)) { - throw new ZWaveError( - `All node IDs must be between 1 and ${MAX_NODES}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.nodeIds = options.nodeIds; } + this.nodeIds = options.nodeIds; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCStaticRouteRequest { + validatePayload(raw.payload.length >= 5); + const nodeIds = [...raw.payload.subarray(0, 5)].filter( + (id) => id > 0 && id <= MAX_NODES, + ); + + return new ZWaveProtocolCCStaticRouteRequest({ + nodeId: ctx.sourceNodeId, + nodeIds, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCLostOptions extends CCCommandOptions { +export interface ZWaveProtocolCCLostOptions { lostNodeId: number; } @CCCommand(ZWaveProtocolCommand.Lost) export class ZWaveProtocolCCLost extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCLostOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.lostNodeId = this.payload[0]; - } else { - this.lostNodeId = options.lostNodeId; - } + super(options); + this.lostNodeId = options.lostNodeId; + } + + public static from(raw: CCRaw, ctx: CCParsingContext): ZWaveProtocolCCLost { + validatePayload(raw.payload.length >= 1); + const lostNodeId = raw.payload[0]; + + return new ZWaveProtocolCCLost({ + nodeId: ctx.sourceNodeId, + lostNodeId, + }); } public lostNodeId: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.lostNodeId]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCAcceptLostOptions extends CCCommandOptions { +export interface ZWaveProtocolCCAcceptLostOptions { accepted: boolean; } @CCCommand(ZWaveProtocolCommand.AcceptLost) export class ZWaveProtocolCCAcceptLost extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCAcceptLostOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - validatePayload( - this.payload[0] === 0x04 || this.payload[0] === 0x05, - ); - this.accepted = this.payload[0] === 0x05; - } else { - this.accepted = options.accepted; - } + super(options); + this.accepted = options.accepted; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCAcceptLost { + validatePayload(raw.payload.length >= 1); + validatePayload( + raw.payload[0] === 0x04 || raw.payload[0] === 0x05, + ); + const accepted = raw.payload[0] === 0x05; + + return new ZWaveProtocolCCAcceptLost({ + nodeId: ctx.sourceNodeId, + accepted, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCNOPPowerOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNOPPowerOptions { powerDampening: number; } @CCCommand(ZWaveProtocolCommand.NOPPower) export class ZWaveProtocolCCNOPPower extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNOPPowerOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - if (this.payload.length >= 2) { - // Ignore byte 0 - this.powerDampening = this.payload[1]; - } else if (this.payload.length === 1) { - this.powerDampening = [ - 0xf0, - 0xc8, - 0xa7, - 0x91, - 0x77, - 0x67, - 0x60, - 0x46, - 0x38, - 0x35, - 0x32, - 0x30, - 0x24, - 0x22, - 0x20, - ].indexOf(this.payload[0]); - if (this.powerDampening === -1) this.powerDampening = 0; - } else { - validatePayload.fail("Invalid payload length!"); - } + super(options); + if (options.powerDampening < 0 || options.powerDampening > 14) { + throw new ZWaveError( + `${this.constructor.name}: power dampening must be between 0 and 14 dBm!`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.powerDampening = options.powerDampening; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNOPPower { + let powerDampening; + + if (raw.payload.length >= 2) { + // Ignore byte 0 + powerDampening = raw.payload[1]; + } else if (raw.payload.length === 1) { + powerDampening = [ + 0xf0, + 0xc8, + 0xa7, + 0x91, + 0x77, + 0x67, + 0x60, + 0x46, + 0x38, + 0x35, + 0x32, + 0x30, + 0x24, + 0x22, + 0x20, + ].indexOf(raw.payload[0]); + if (powerDampening === -1) powerDampening = 0; } else { - if (options.powerDampening < 0 || options.powerDampening > 14) { - throw new ZWaveError( - `${this.constructor.name}: power dampening must be between 0 and 14 dBm!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.powerDampening = options.powerDampening; + validatePayload.fail("Invalid payload length!"); } + + return new ZWaveProtocolCCNOPPower({ + nodeId: ctx.sourceNodeId, + powerDampening, + }); } // 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); } } // @publicAPI -export interface ZWaveProtocolCCReservedIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCReservedIDsOptions { reservedNodeIDs: number[]; } @CCCommand(ZWaveProtocolCommand.ReservedIDs) export class ZWaveProtocolCCReservedIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCReservedIDsOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - const numNodeIDs = this.payload[0]; - validatePayload(this.payload.length >= 1 + numNodeIDs); - this.reservedNodeIDs = [ - ...this.payload.subarray(1, 1 + numNodeIDs), - ]; - } else { - this.reservedNodeIDs = options.reservedNodeIDs; - } + super(options); + this.reservedNodeIDs = options.reservedNodeIDs; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCReservedIDs { + validatePayload(raw.payload.length >= 1); + const numNodeIDs = raw.payload[0]; + validatePayload(raw.payload.length >= 1 + numNodeIDs); + const reservedNodeIDs = [ + ...raw.payload.subarray(1, 1 + numNodeIDs), + ]; + + return new ZWaveProtocolCCReservedIDs({ + nodeId: ctx.sourceNodeId, + reservedNodeIDs, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCReserveNodeIDsOptions extends CCCommandOptions { +export interface ZWaveProtocolCCReserveNodeIDsOptions { numNodeIDs: number; } @@ -1069,32 +1173,35 @@ export interface ZWaveProtocolCCReserveNodeIDsOptions extends CCCommandOptions { @expectedCCResponse(ZWaveProtocolCCReservedIDs) export class ZWaveProtocolCCReserveNodeIDs extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCReserveNodeIDsOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 1); - this.numNodeIDs = this.payload[0]; - } else { - this.numNodeIDs = options.numNodeIDs; - } + super(options); + this.numNodeIDs = options.numNodeIDs; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCReserveNodeIDs { + validatePayload(raw.payload.length >= 1); + const numNodeIDs = raw.payload[0]; + + return new ZWaveProtocolCCReserveNodeIDs({ + nodeId: ctx.sourceNodeId, + numNodeIDs, + }); } public numNodeIDs: number; - public serialize(): Buffer { + public serialize(ctx: CCEncodingContext): Buffer { this.payload = Buffer.from([this.numNodeIDs]); - return super.serialize(); + return super.serialize(ctx); } } // @publicAPI -export interface ZWaveProtocolCCNodesExistReplyOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCNodesExistReplyOptions { nodeMaskType: number; nodeListUpdated: boolean; } @@ -1102,31 +1209,37 @@ export interface ZWaveProtocolCCNodesExistReplyOptions @CCCommand(ZWaveProtocolCommand.NodesExistReply) export class ZWaveProtocolCCNodesExistReply extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNodesExistReplyOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.nodeMaskType = this.payload[0]; - this.nodeListUpdated = this.payload[1] === 0x01; - } else { - this.nodeMaskType = options.nodeMaskType; - this.nodeListUpdated = options.nodeListUpdated; - } + super(options); + this.nodeMaskType = options.nodeMaskType; + this.nodeListUpdated = options.nodeListUpdated; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNodesExistReply { + validatePayload(raw.payload.length >= 2); + const nodeMaskType = raw.payload[0]; + const nodeListUpdated = raw.payload[1] === 0x01; + + return new ZWaveProtocolCCNodesExistReply({ + nodeId: ctx.sourceNodeId, + nodeMaskType, + nodeListUpdated, + }); } 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); } } @@ -1138,7 +1251,7 @@ function testResponseForZWaveProtocolNodesExist( } // @publicAPI -export interface ZWaveProtocolCCNodesExistOptions extends CCCommandOptions { +export interface ZWaveProtocolCCNodesExistOptions { nodeMaskType: number; nodeIDs: number[]; } @@ -1150,39 +1263,45 @@ export interface ZWaveProtocolCCNodesExistOptions extends CCCommandOptions { ) export class ZWaveProtocolCCNodesExist extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCNodesExistOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.nodeMaskType = this.payload[0]; - const numNodeIDs = this.payload[1]; - validatePayload(this.payload.length >= 2 + numNodeIDs); - this.nodeIDs = [...this.payload.subarray(2, 2 + numNodeIDs)]; - } else { - this.nodeMaskType = options.nodeMaskType; - this.nodeIDs = options.nodeIDs; - } + super(options); + this.nodeMaskType = options.nodeMaskType; + this.nodeIDs = options.nodeIDs; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCNodesExist { + validatePayload(raw.payload.length >= 2); + const nodeMaskType = raw.payload[0]; + const numNodeIDs = raw.payload[1]; + validatePayload(raw.payload.length >= 2 + numNodeIDs); + const nodeIDs = [...raw.payload.subarray(2, 2 + numNodeIDs)]; + + return new ZWaveProtocolCCNodesExist({ + nodeId: ctx.sourceNodeId, + nodeMaskType, + nodeIDs, + }); } 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); } } // @publicAPI -export interface ZWaveProtocolCCSetNWIModeOptions extends CCCommandOptions { +export interface ZWaveProtocolCCSetNWIModeOptions { enabled: boolean; timeoutMinutes?: number; } @@ -1190,31 +1309,37 @@ export interface ZWaveProtocolCCSetNWIModeOptions extends CCCommandOptions { @CCCommand(ZWaveProtocolCommand.SetNWIMode) export class ZWaveProtocolCCSetNWIMode extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCSetNWIModeOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.enabled = this.payload[0] === 0x01; - this.timeoutMinutes = this.payload[1] || undefined; - } else { - this.enabled = options.enabled; - this.timeoutMinutes = options.timeoutMinutes; - } + super(options); + this.enabled = options.enabled; + this.timeoutMinutes = options.timeoutMinutes; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCSetNWIMode { + validatePayload(raw.payload.length >= 2); + const enabled = raw.payload[0] === 0x01; + const timeoutMinutes: number | undefined = raw.payload[1] || undefined; + + return new ZWaveProtocolCCSetNWIMode({ + nodeId: ctx.sourceNodeId, + enabled, + timeoutMinutes, + }); } 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); } } @@ -1224,9 +1349,7 @@ export class ZWaveProtocolCCExcludeRequest {} // @publicAPI -export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions { targetNodeId: number; routeNumber: number; } @@ -1234,28 +1357,34 @@ export interface ZWaveProtocolCCAssignReturnRoutePriorityOptions @CCCommand(ZWaveProtocolCommand.AssignReturnRoutePriority) export class ZWaveProtocolCCAssignReturnRoutePriority extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCAssignReturnRoutePriorityOptions, + options: WithAddress, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.targetNodeId = this.payload[0]; - this.routeNumber = this.payload[1]; - } else { - this.targetNodeId = options.targetNodeId; - this.routeNumber = options.routeNumber; - } + super(options); + this.targetNodeId = options.targetNodeId; + this.routeNumber = options.routeNumber; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCAssignReturnRoutePriority { + validatePayload(raw.payload.length >= 2); + const targetNodeId = raw.payload[0]; + const routeNumber = raw.payload[1]; + + return new ZWaveProtocolCCAssignReturnRoutePriority({ + nodeId: ctx.sourceNodeId, + targetNodeId, + routeNumber, + }); } 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); } } @@ -1265,9 +1394,7 @@ export class ZWaveProtocolCCAssignSUCReturnRoutePriority {} // @publicAPI -export interface ZWaveProtocolCCSmartStartIncludedNodeInformationOptions - extends CCCommandOptions -{ +export interface ZWaveProtocolCCSmartStartIncludedNodeInformationOptions { nwiHomeId: Buffer; } @@ -1276,31 +1403,38 @@ export class ZWaveProtocolCCSmartStartIncludedNodeInformation extends ZWaveProtocolCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | ZWaveProtocolCCSmartStartIncludedNodeInformationOptions, + options: WithAddress< + ZWaveProtocolCCSmartStartIncludedNodeInformationOptions + >, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 4); - this.nwiHomeId = this.payload.subarray(0, 4); - } else { - if (options.nwiHomeId.length !== 4) { - throw new ZWaveError( - `nwiHomeId must have length 4`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.nwiHomeId = options.nwiHomeId; + super(options); + if (options.nwiHomeId.length !== 4) { + throw new ZWaveError( + `nwiHomeId must have length 4`, + ZWaveErrorCodes.Argument_Invalid, + ); } + this.nwiHomeId = options.nwiHomeId; + } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ZWaveProtocolCCSmartStartIncludedNodeInformation { + validatePayload(raw.payload.length >= 4); + const nwiHomeId: Buffer = raw.payload.subarray(0, 4); + + return new ZWaveProtocolCCSmartStartIncludedNodeInformation({ + nodeId: ctx.sourceNodeId, + nwiHomeId, + }); } 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..459dff05b456 100644 --- a/packages/cc/src/cc/index.ts +++ b/packages/cc/src/cc/index.ts @@ -1,7 +1,11 @@ // This file is auto-generated by maintenance/generateCCExports.ts // Do not edit it by hand or your changes will be lost! -export type { AlarmSensorCCGetOptions } from "./AlarmSensorCC"; +export type { + AlarmSensorCCGetOptions, + AlarmSensorCCReportOptions, + AlarmSensorCCSupportedReportOptions, +} from "./AlarmSensorCC"; export { AlarmSensorCC, AlarmSensorCCGet, @@ -13,7 +17,7 @@ export { export type { AssociationCCGetOptions, AssociationCCRemoveOptions, - AssociationCCReportSpecificOptions, + AssociationCCReportOptions, AssociationCCSetOptions, AssociationCCSpecificGroupReportOptions, AssociationCCSupportedGroupingsReportOptions, @@ -34,7 +38,7 @@ export type { AssociationGroupInfoCCCommandListGetOptions, AssociationGroupInfoCCCommandListReportOptions, AssociationGroupInfoCCInfoGetOptions, - AssociationGroupInfoCCInfoReportSpecificOptions, + AssociationGroupInfoCCInfoReportOptions, AssociationGroupInfoCCNameGetOptions, AssociationGroupInfoCCNameReportOptions, } from "./AssociationGroupInfoCC"; @@ -50,8 +54,11 @@ export { } from "./AssociationGroupInfoCC"; export type { BarrierOperatorCCEventSignalingGetOptions, + BarrierOperatorCCEventSignalingReportOptions, BarrierOperatorCCEventSignalingSetOptions, + BarrierOperatorCCReportOptions, BarrierOperatorCCSetOptions, + BarrierOperatorCCSignalingCapabilitiesReportOptions, } from "./BarrierOperatorCC"; export { BarrierOperatorCC, @@ -73,7 +80,10 @@ export { BasicCCSet, BasicCCValues, } from "./BasicCC"; -export type { BatteryCCReportOptions } from "./BatteryCC"; +export type { + BatteryCCHealthReportOptions, + BatteryCCReportOptions, +} from "./BatteryCC"; export { BatteryCC, BatteryCCGet, @@ -108,7 +118,12 @@ export { } from "./BinarySwitchCC"; export type { CRC16CCCommandEncapsulationOptions } from "./CRC16CC"; export { CRC16CC, CRC16CCCommandEncapsulation } from "./CRC16CC"; -export type { CentralSceneCCConfigurationSetOptions } from "./CentralSceneCC"; +export type { + CentralSceneCCConfigurationReportOptions, + CentralSceneCCConfigurationSetOptions, + CentralSceneCCNotificationOptions, + CentralSceneCCSupportedReportOptions, +} from "./CentralSceneCC"; export { CentralSceneCC, CentralSceneCCConfigurationGet, @@ -120,8 +135,11 @@ export { CentralSceneCCValues, } from "./CentralSceneCC"; export type { + ClimateControlScheduleCCChangedReportOptions, ClimateControlScheduleCCGetOptions, + ClimateControlScheduleCCOverrideReportOptions, ClimateControlScheduleCCOverrideSetOptions, + ClimateControlScheduleCCReportOptions, ClimateControlScheduleCCSetOptions, } from "./ClimateControlScheduleCC"; export { @@ -136,7 +154,7 @@ export { ClimateControlScheduleCCSet, ClimateControlScheduleCCValues, } from "./ClimateControlScheduleCC"; -export type { ClockCCSetOptions } from "./ClockCC"; +export type { ClockCCReportOptions, ClockCCSetOptions } from "./ClockCC"; export { ClockCC, ClockCCGet, ClockCCReport, ClockCCSet } from "./ClockCC"; export type { ColorSwitchCCGetOptions, @@ -160,6 +178,7 @@ export { export type { ConfigurationCCAPISetOptions, ConfigurationCCBulkGetOptions, + ConfigurationCCBulkReportOptions, ConfigurationCCBulkSetOptions, ConfigurationCCGetOptions, ConfigurationCCInfoReportOptions, @@ -190,7 +209,10 @@ export { DeviceResetLocallyCCNotification, } from "./DeviceResetLocallyCC"; export type { + DoorLockCCCapabilitiesReportOptions, + DoorLockCCConfigurationReportOptions, DoorLockCCConfigurationSetOptions, + DoorLockCCOperationReportOptions, DoorLockCCOperationSetOptions, } from "./DoorLockCC"; export { @@ -205,7 +227,11 @@ export { DoorLockCCOperationSet, DoorLockCCValues, } from "./DoorLockCC"; -export type { DoorLockLoggingCCRecordGetOptions } from "./DoorLockLoggingCC"; +export type { + DoorLockLoggingCCRecordGetOptions, + DoorLockLoggingCCRecordReportOptions, + DoorLockLoggingCCRecordsSupportedReportOptions, +} from "./DoorLockLoggingCC"; export { DoorLockLoggingCC, DoorLockLoggingCCRecordGet, @@ -224,7 +250,13 @@ export { EnergyProductionCCReport, EnergyProductionCCValues, } from "./EnergyProductionCC"; -export type { EntryControlCCConfigurationSetOptions } from "./EntryControlCC"; +export type { + EntryControlCCConfigurationReportOptions, + EntryControlCCConfigurationSetOptions, + EntryControlCCEventSupportedReportOptions, + EntryControlCCKeySupportedReportOptions, + EntryControlCCNotificationOptions, +} from "./EntryControlCC"; export { EntryControlCC, EntryControlCCConfigurationGet, @@ -238,11 +270,16 @@ export { EntryControlCCValues, } from "./EntryControlCC"; export type { + FirmwareUpdateMetaDataCCActivationReportOptions, FirmwareUpdateMetaDataCCActivationSetOptions, + FirmwareUpdateMetaDataCCGetOptions, FirmwareUpdateMetaDataCCMetaDataReportOptions, FirmwareUpdateMetaDataCCPrepareGetOptions, + FirmwareUpdateMetaDataCCPrepareReportOptions, FirmwareUpdateMetaDataCCReportOptions, FirmwareUpdateMetaDataCCRequestGetOptions, + FirmwareUpdateMetaDataCCRequestReportOptions, + FirmwareUpdateMetaDataCCStatusReportOptions, } from "./FirmwareUpdateMetaDataCC"; export { FirmwareUpdateMetaDataCC, @@ -260,7 +297,11 @@ export { FirmwareUpdateMetaDataCCValues, } from "./FirmwareUpdateMetaDataCC"; export { HailCC } from "./HailCC"; -export type { HumidityControlModeCCSetOptions } from "./HumidityControlModeCC"; +export type { + HumidityControlModeCCReportOptions, + HumidityControlModeCCSetOptions, + HumidityControlModeCCSupportedReportOptions, +} from "./HumidityControlModeCC"; export { HumidityControlModeCC, HumidityControlModeCCGet, @@ -270,6 +311,7 @@ export { HumidityControlModeCCSupportedReport, HumidityControlModeCCValues, } from "./HumidityControlModeCC"; +export type { HumidityControlOperatingStateCCReportOptions } from "./HumidityControlOperatingStateCC"; export { HumidityControlOperatingStateCC, HumidityControlOperatingStateCCGet, @@ -278,9 +320,13 @@ export { } from "./HumidityControlOperatingStateCC"; export type { HumidityControlSetpointCCCapabilitiesGetOptions, + HumidityControlSetpointCCCapabilitiesReportOptions, HumidityControlSetpointCCGetOptions, + HumidityControlSetpointCCReportOptions, HumidityControlSetpointCCScaleSupportedGetOptions, + HumidityControlSetpointCCScaleSupportedReportOptions, HumidityControlSetpointCCSetOptions, + HumidityControlSetpointCCSupportedReportOptions, } from "./HumidityControlSetpointCC"; export { HumidityControlSetpointCC, @@ -308,10 +354,11 @@ export type { IndicatorCCDescriptionGetOptions, IndicatorCCDescriptionReportOptions, IndicatorCCGetOptions, - IndicatorCCReportSpecificOptions, + IndicatorCCReportOptions, IndicatorCCSetOptions, IndicatorCCSupportedGetOptions, IndicatorCCSupportedReportOptions, + IndicatorObject, } from "./IndicatorCC"; export { IndicatorCC, @@ -325,13 +372,19 @@ export { IndicatorCCValues, } from "./IndicatorCC"; export type { + IrrigationCCSystemConfigReportOptions, IrrigationCCSystemConfigSetOptions, + IrrigationCCSystemInfoReportOptions, IrrigationCCSystemShutoffOptions, + IrrigationCCSystemStatusReportOptions, IrrigationCCValveConfigGetOptions, + IrrigationCCValveConfigReportOptions, IrrigationCCValveConfigSetOptions, IrrigationCCValveInfoGetOptions, + IrrigationCCValveInfoReportOptions, IrrigationCCValveRunOptions, IrrigationCCValveTableGetOptions, + IrrigationCCValveTableReportOptions, IrrigationCCValveTableRunOptions, IrrigationCCValveTableSetOptions, } from "./IrrigationCC"; @@ -357,7 +410,10 @@ export { IrrigationCCValveTableRun, IrrigationCCValveTableSet, } from "./IrrigationCC"; -export type { LanguageCCSetOptions } from "./LanguageCC"; +export type { + LanguageCCReportOptions, + LanguageCCSetOptions, +} from "./LanguageCC"; export { LanguageCC, LanguageCCGet, @@ -365,7 +421,7 @@ export { LanguageCCSet, LanguageCCValues, } from "./LanguageCC"; -export type { LockCCSetOptions } from "./LockCC"; +export type { LockCCReportOptions, LockCCSetOptions } from "./LockCC"; export { LockCC, LockCCGet, @@ -377,6 +433,7 @@ export type { ManufacturerProprietaryCCOptions } from "./ManufacturerProprietary export { ManufacturerProprietaryCC } from "./ManufacturerProprietaryCC"; export type { ManufacturerSpecificCCDeviceSpecificGetOptions, + ManufacturerSpecificCCDeviceSpecificReportOptions, ManufacturerSpecificCCReportOptions, } from "./ManufacturerSpecificCC"; export { @@ -421,6 +478,7 @@ export { } from "./MultiChannelAssociationCC"; export type { MultiChannelCCAggregatedMembersGetOptions, + MultiChannelCCAggregatedMembersReportOptions, MultiChannelCCCapabilityGetOptions, MultiChannelCCCapabilityReportOptions, MultiChannelCCCommandEncapsulationOptions, @@ -429,6 +487,7 @@ export type { MultiChannelCCEndPointReportOptions, MultiChannelCCV1CommandEncapsulationOptions, MultiChannelCCV1GetOptions, + MultiChannelCCV1ReportOptions, } from "./MultiChannelCC"; export { MultiChannelCC, @@ -485,9 +544,11 @@ export { MultilevelSwitchCCSupportedReport, MultilevelSwitchCCValues, } from "./MultilevelSwitchCC"; -export { NoOperationCC, messageIsPing } from "./NoOperationCC"; +export { NoOperationCC } from "./NoOperationCC"; export type { + NodeNamingAndLocationCCLocationReportOptions, NodeNamingAndLocationCCLocationSetOptions, + NodeNamingAndLocationCCNameReportOptions, NodeNamingAndLocationCCNameSetOptions, } from "./NodeNamingCC"; export { @@ -535,8 +596,12 @@ export { PowerlevelCCTestNodeSet, } from "./PowerlevelCC"; export type { + ProtectionCCExclusiveControlReportOptions, ProtectionCCExclusiveControlSetOptions, + ProtectionCCReportOptions, ProtectionCCSetOptions, + ProtectionCCSupportedReportOptions, + ProtectionCCTimeoutReportOptions, ProtectionCCTimeoutSetOptions, } from "./ProtectionCC"; export { @@ -562,6 +627,7 @@ export { } from "./SceneActivationCC"; export type { SceneActuatorConfigurationCCGetOptions, + SceneActuatorConfigurationCCReportOptions, SceneActuatorConfigurationCCSetOptions, } from "./SceneActuatorConfigurationCC"; export { @@ -573,6 +639,7 @@ export { } from "./SceneActuatorConfigurationCC"; export type { SceneControllerConfigurationCCGetOptions, + SceneControllerConfigurationCCReportOptions, SceneControllerConfigurationCCSetOptions, } from "./SceneControllerConfigurationCC"; export { @@ -628,6 +695,7 @@ export type { Security2CCMessageEncapsulationOptions, Security2CCNetworkKeyGetOptions, Security2CCNetworkKeyReportOptions, + Security2CCNonceGetOptions, Security2CCNonceReportOptions, Security2CCPublicKeyReportOptions, Security2CCTransferEndOptions, @@ -701,7 +769,11 @@ export { SupervisionCCReport, SupervisionCCValues, } from "./SupervisionCC"; -export type { ThermostatFanModeCCSetOptions } from "./ThermostatFanModeCC"; +export type { + ThermostatFanModeCCReportOptions, + ThermostatFanModeCCSetOptions, + ThermostatFanModeCCSupportedReportOptions, +} from "./ThermostatFanModeCC"; export { ThermostatFanModeCC, ThermostatFanModeCCGet, @@ -711,6 +783,7 @@ export { ThermostatFanModeCCSupportedReport, ThermostatFanModeCCValues, } from "./ThermostatFanModeCC"; +export type { ThermostatFanStateCCReportOptions } from "./ThermostatFanStateCC"; export { ThermostatFanStateCC, ThermostatFanStateCCGet, @@ -731,6 +804,7 @@ export { ThermostatModeCCSupportedReport, ThermostatModeCCValues, } from "./ThermostatModeCC"; +export type { ThermostatOperatingStateCCReportOptions } from "./ThermostatOperatingStateCC"; export { ThermostatOperatingStateCC, ThermostatOperatingStateCCGet, @@ -782,7 +856,10 @@ export { TimeCCTimeOffsetSet, TimeCCTimeReport, } from "./TimeCC"; -export type { TimeParametersCCSetOptions } from "./TimeParametersCC"; +export type { + TimeParametersCCReportOptions, + TimeParametersCCSetOptions, +} from "./TimeParametersCC"; export { TimeParametersCC, TimeParametersCCGet, @@ -812,6 +889,7 @@ export type { UserCodeCCAdminCodeSetOptions, UserCodeCCCapabilitiesReportOptions, UserCodeCCExtendedUserCodeGetOptions, + UserCodeCCExtendedUserCodeReportOptions, UserCodeCCExtendedUserCodeSetOptions, UserCodeCCGetOptions, UserCodeCCKeypadModeReportOptions, @@ -848,6 +926,7 @@ export type { VersionCCCommandClassGetOptions, VersionCCCommandClassReportOptions, VersionCCReportOptions, + VersionCCZWaveSoftwareReportOptions, } from "./VersionCC"; export { VersionCC, @@ -861,7 +940,11 @@ export { VersionCCZWaveSoftwareGet, VersionCCZWaveSoftwareReport, } from "./VersionCC"; -export type { WakeUpCCIntervalSetOptions } from "./WakeUpCC"; +export type { + WakeUpCCIntervalCapabilitiesReportOptions, + WakeUpCCIntervalReportOptions, + WakeUpCCIntervalSetOptions, +} from "./WakeUpCC"; export { WakeUpCC, WakeUpCCIntervalCapabilitiesGet, @@ -875,6 +958,7 @@ export { } from "./WakeUpCC"; export type { WindowCoveringCCGetOptions, + WindowCoveringCCReportOptions, WindowCoveringCCSetOptions, WindowCoveringCCStartLevelChangeOptions, WindowCoveringCCStopLevelChangeOptions, @@ -980,7 +1064,10 @@ export { manufacturerId, manufacturerProprietaryAPI, } from "./manufacturerProprietary/Decorators"; -export type { FibaroVenetianBlindCCSetOptions } from "./manufacturerProprietary/FibaroCC"; +export type { + FibaroVenetianBlindCCReportOptions, + FibaroVenetianBlindCCSetOptions, +} from "./manufacturerProprietary/FibaroCC"; export { FibaroCC, FibaroVenetianBlindCC, diff --git a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts index 01ff2bccef2d..b7d2a5283568 100644 --- a/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts +++ b/packages/cc/src/cc/manufacturerProprietary/FibaroCC.ts @@ -5,15 +5,17 @@ import { type MessageRecord, type ValueID, ValueMetadata, + type WithAddress, ZWaveError, ZWaveErrorCodes, parseMaybeNumber, validatePayload, } from "@zwave-js/core/safe"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetValueDB, } from "@zwave-js/host/safe"; import { pick } from "@zwave-js/shared"; import { validateArgs } from "@zwave-js/transformers"; @@ -29,9 +31,11 @@ import { throwWrongValueType, } from "../../lib/API"; import { - type CCCommandOptions, - type CommandClassDeserializationOptions, - gotDeserializationOptions, + type CCRaw, + type CommandClassOptions, + type InterviewContext, + type PersistValuesContext, + type RefreshValuesContext, } from "../../lib/CommandClass"; import { expectedCCResponse } from "../../lib/CommandClassDecorators"; import { @@ -89,6 +93,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 +115,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, + endpointIndex: this.endpoint.index, }); - const response = await this.applHost.sendCommand< + const response = await this.host.sendCommand< FibaroVenetianBlindCCReport >( cc, @@ -114,22 +132,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, + endpointIndex: 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, + endpointIndex: 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,84 +219,71 @@ export class FibaroCCAPI extends ManufacturerProprietaryCCAPI { @manufacturerId(MANUFACTURERID_FIBARO) export class FibaroCC extends ManufacturerProprietaryCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, + options: CommandClassOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - validatePayload(this.payload.length >= 2); - this.fibaroCCId = this.payload[0]; - this.fibaroCCCommand = this.payload[1]; - - const FibaroConstructor = getFibaroCCCommandConstructor( - this.fibaroCCId, - this.fibaroCCCommand, - ); - if ( - FibaroConstructor - && (new.target as any) !== FibaroConstructor - ) { - return new FibaroConstructor(host, options); - } + super(options); - this.payload = this.payload.subarray(2); - } else { - this.fibaroCCId = getFibaroCCId(this); - this.fibaroCCCommand = getFibaroCCCommand(this); - } + this.fibaroCCId = getFibaroCCId(this); + this.fibaroCCCommand = getFibaroCCCommand(this); } - public fibaroCCId?: number; - public fibaroCCCommand?: number; + public static from(raw: CCRaw, ctx: CCParsingContext): FibaroCC { + validatePayload(raw.payload.length >= 2); + const fibaroCCId = raw.payload[0]; + const fibaroCCCommand = raw.payload[1]; - 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[]; + const FibaroConstructor = getFibaroCCCommandConstructor( + fibaroCCId, + fibaroCCCommand, + ); + if (FibaroConstructor) { + return FibaroConstructor.from( + raw.withPayload(raw.payload.subarray(2)), + ctx, + ); } - return []; + return new FibaroCC({ + nodeId: ctx.sourceNodeId, + }); } - public async interview(applHost: ZWaveApplicationHost): Promise { - const node = this.getNode(applHost)!; + public fibaroCCId?: number; + public fibaroCCCommand?: number; + + 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 +299,7 @@ export class FibaroCC extends ManufacturerProprietaryCC { Buffer.from([this.fibaroCCId, this.fibaroCCCommand]), this.payload, ]); - return super.serialize(); + return super.serialize(ctx); } } @@ -310,53 +315,43 @@ export class FibaroVenetianBlindCC extends FibaroCC { declare fibaroCCCommand: FibaroVenetianBlindCCCommand; public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, + options: CommandClassOptions, ) { - super(host, options); + super(options); this.fibaroCCId = FibaroCCIDs.VenetianBlind; - - if (gotDeserializationOptions(options)) { - if ( - this.fibaroCCCommand === FibaroVenetianBlindCCCommand.Report - && (new.target as any) !== FibaroVenetianBlindCCReport - ) { - return new FibaroVenetianBlindCCReport(host, 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, + endpointIndex: this.endpointIndex, }), ); if (resp) { 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", }); @@ -366,47 +361,44 @@ tilt: ${resp.tilt}`; // @publicAPI export type FibaroVenetianBlindCCSetOptions = - & CCCommandOptions - & ( - | { - position: number; - } - | { - tilt: number; - } - | { - position: number; - tilt: number; - } - ); + | { + position: number; + } + | { + tilt: number; + } + | { + position: number; + tilt: number; + }; @fibaroCCCommand(FibaroVenetianBlindCCCommand.Set) export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, - options: - | CommandClassDeserializationOptions - | FibaroVenetianBlindCCSetOptions, + options: WithAddress, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Set; - if (Buffer.isBuffer(options)) { - // TODO: Deserialize payload - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - if ("position" in options) this.position = options.position; - if ("tilt" in options) this.tilt = options.tilt; - } + if ("position" in options) this.position = options.position; + if ("tilt" in options) this.tilt = options.tilt; + } + + public static from( + _raw: CCRaw, + _ctx: CCParsingContext, + ): FibaroVenetianBlindCCSet { + // TODO: Deserialize payload + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); } 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 +406,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,36 +418,58 @@ export class FibaroVenetianBlindCCSet extends FibaroVenetianBlindCC { message.tilt = this.tilt; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } } +// @publicAPI +export interface FibaroVenetianBlindCCReportOptions { + position?: MaybeUnknown; + tilt?: MaybeUnknown; +} + @fibaroCCCommand(FibaroVenetianBlindCCCommand.Report) export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions, + options: WithAddress, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Report; - validatePayload(this.payload.length >= 3); + // TODO: Check implementation: + this.position = options.position; + this.tilt = options.tilt; + } + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FibaroVenetianBlindCCReport { + validatePayload(raw.payload.length >= 3); // When the node sends a report, payload[0] === 0b11. This is probably a // bit mask for position and tilt - if (!!(this.payload[0] & 0b10)) { - this._position = parseMaybeNumber(this.payload[1]); + let position: MaybeUnknown | undefined; + if (!!(raw.payload[0] & 0b10)) { + position = parseMaybeNumber(raw.payload[1]); } - if (!!(this.payload[0] & 0b01)) { - this._tilt = parseMaybeNumber(this.payload[2]); + + let tilt: MaybeUnknown | undefined; + if (!!(raw.payload[0] & 0b01)) { + tilt = parseMaybeNumber(raw.payload[2]); } + + return new FibaroVenetianBlindCCReport({ + nodeId: ctx.sourceNodeId, + position, + tilt, + }); } - 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( @@ -481,17 +495,10 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { return true; } - private _position: MaybeUnknown | undefined; - public get position(): MaybeUnknown | undefined { - return this._position; - } + public position: MaybeUnknown | undefined; + public tilt: MaybeUnknown | undefined; - private _tilt: MaybeUnknown | undefined; - public get tilt(): MaybeUnknown | undefined { - 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 +507,7 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { message.tilt = this.tilt; } return { - ...super.toLogEntry(host), + ...super.toLogEntry(ctx), message, }; } @@ -510,10 +517,18 @@ export class FibaroVenetianBlindCCReport extends FibaroVenetianBlindCC { @expectedCCResponse(FibaroVenetianBlindCCReport) export class FibaroVenetianBlindCCGet extends FibaroVenetianBlindCC { public constructor( - host: ZWaveHost, - options: CommandClassDeserializationOptions | CCCommandOptions, + options: CommandClassOptions, ) { - super(host, options); + super(options); this.fibaroCCCommand = FibaroVenetianBlindCCCommand.Get; } + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): FibaroVenetianBlindCCGet { + return new FibaroVenetianBlindCCGet({ + nodeId: ctx.sourceNodeId, + }); + } } diff --git a/packages/cc/src/index.ts b/packages/cc/src/index.ts index 0338ed9f54b8..c87f1b0b4cc5 100644 --- a/packages/cc/src/index.ts +++ b/packages/cc/src/index.ts @@ -6,7 +6,6 @@ export * from "./lib/API"; export * from "./lib/CommandClass"; export * from "./lib/CommandClassDecorators"; export * from "./lib/EncapsulatingCommandClass"; -export * from "./lib/ICommandClassContainer"; export { MGRPExtension, MOSExtension, 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..7ae4b2f9668b 100644 --- a/packages/cc/src/lib/CommandClass.ts +++ b/packages/cc/src/lib/CommandClass.ts @@ -1,18 +1,29 @@ import { type BroadcastCC, + type CCAddress, + 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,11 +35,17 @@ import { valueIdToString, } from "@zwave-js/core"; import type { - ZWaveApplicationHost, - ZWaveHost, - ZWaveValueHost, + CCEncodingContext, + CCParsingContext, + GetDeviceConfig, + GetInterviewOptions, + GetNode, + GetSupportedCCVersion, + GetValueDB, + HostIDs, + LogNode, + LookupManufacturer, } from "@zwave-js/host"; -import { MessageOrigin } from "@zwave-js/serial"; import { type JSONObject, buffer2hex, @@ -37,7 +54,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, @@ -54,10 +71,6 @@ import { isEncapsulatingCommandClass, isMultiEncapsulatingCommandClass, } from "./EncapsulatingCommandClass"; -import { - type ICommandClassContainer, - isCommandClassContainer, -} from "./ICommandClassContainer"; import { type CCValue, type DynamicCCValue, @@ -65,164 +78,205 @@ import { defaultCCValueOptions, } from "./Values"; -export type CommandClassDeserializationOptions = - & { - data: Buffer; - origin?: MessageOrigin; - /** If known, the frame type of the containing message */ - frameType?: FrameType; - } - & ( - | { - fromEncapsulation?: false; - nodeId: number; - } - | { - fromEncapsulation: true; - encapCC: CommandClass; - } - ); - -export function gotDeserializationOptions( - options: CommandClassOptions, -): options is CommandClassDeserializationOptions { - return "data" in options && Buffer.isBuffer(options.data); -} - -export interface CCCommandOptions { - nodeId: number | MulticastDestination; - endpoint?: number; -} - -interface CommandClassCreationOptions extends CCCommandOptions { +export interface CommandClassOptions extends CCAddress { ccId?: number; // Used to overwrite the declared CC ID ccCommand?: number; // undefined = NoOp payload?: Buffer; - origin?: undefined; } -function gotCCCommandOptions(options: any): options is CCCommandOptions { - return typeof options.nodeId === "number" || isArray(options.nodeId); +// 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 = + & HostIDs + & 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)); } -export type CommandClassOptions = - | CommandClassCreationOptions - | CommandClassDeserializationOptions; +export class CCRaw { + public constructor( + public ccId: CommandClasses, + public ccCommand: number | undefined, + public payload: Buffer, + ) {} + + public static parse(data: Buffer): CCRaw { + const { ccId, bytesRead: ccIdLength } = parseCCId(data); + // There are so few exceptions that we can handle them here manually + if (ccId === CommandClasses["No Operation"]) { + return new CCRaw(ccId, undefined, Buffer.allocUnsafe(0)); + } + let ccCommand: number | undefined = data[ccIdLength]; + let payload = data.subarray(ccIdLength + 1); + if (ccId === CommandClasses["Transport Service"]) { + // Transport Service only uses the higher 5 bits for the command + // and re-uses the lower 3 bits of the ccCommand as payload + payload = Buffer.concat([ + Buffer.from([ccCommand & 0b111]), + payload, + ]); + ccCommand = ccCommand & 0b11111_000; + } else if (ccId === CommandClasses["Manufacturer Proprietary"]) { + // ManufacturerProprietaryCC has no CC command, so the first + // payload byte is stored in ccCommand. + payload = Buffer.concat([ + Buffer.from([ccCommand]), + payload, + ]); + ccCommand = undefined; + } + + return new CCRaw(ccId, ccCommand, payload); + } + + public withPayload(payload: Buffer): CCRaw { + return new CCRaw(this.ccId, this.ccCommand, payload); + } +} // @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; - // 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 }); - - if (gotDeserializationOptions(options)) { - // For deserialized commands, try to invoke the correct subclass constructor - const CCConstructor = - getCCConstructor(CommandClass.getCommandClass(options.data)) - ?? CommandClass; - const ccId = CommandClass.getCommandClass(options.data); - const ccCommand = CCConstructor.getCCCommand(options.data); - if (ccCommand != undefined) { - const CommandConstructor = getCCCommandConstructor( - ccId, - ccCommand, - ); - if ( - CommandConstructor - && (new.target as any) !== CommandConstructor - ) { - return new CommandConstructor(host, options); - } - } - - // If the constructor is correct or none was found, fall back to normal deserialization - if (options.fromEncapsulation) { - // Propagate the node ID and endpoint index from the encapsulating CC - this.nodeId = options.encapCC.nodeId; - if (!this.endpointIndex && options.encapCC.endpointIndex) { - this.endpointIndex = options.encapCC.endpointIndex; - } - // And remember which CC encapsulates this CC - this.encapsulatingCC = options.encapCC as any; - } else { - this.nodeId = options.nodeId; - } + public constructor(options: CommandClassOptions) { + const { + nodeId, + endpointIndex = 0, + ccId = getCommandClass(this), + ccCommand = getCCCommand(this), + payload = Buffer.allocUnsafe(0), + } = options; + + this.nodeId = nodeId; + this.endpointIndex = endpointIndex; + this.ccId = ccId; + this.ccCommand = ccCommand; + this.payload = payload; + } + + public static parse( + data: Buffer, + ctx: CCParsingContext, + ): CommandClass { + const raw = CCRaw.parse(data); - this.frameType = options.frameType; - - ({ - ccId: this.ccId, - ccCommand: this.ccCommand, - payload: this.payload, - } = this.deserialize(options.data)); - } else if (gotCCCommandOptions(options)) { - const { - nodeId, - endpoint = 0, - ccId = getCommandClass(this), - ccCommand = getCCCommand(this), - payload = Buffer.allocUnsafe(0), - } = options; - this.nodeId = nodeId; - this.endpointIndex = endpoint; - this.ccId = ccId; - this.ccCommand = ccCommand; - this.payload = payload; + // Find the correct subclass constructor to invoke + const CCConstructor = getCCConstructor(raw.ccId); + if (!CCConstructor) { + // None -> fall back to the default constructor + return CommandClass.from(raw, ctx); } - if (this instanceof InvalidCC) return; + let CommandConstructor: CCConstructor | undefined; + if (raw.ccCommand != undefined) { + CommandConstructor = getCCCommandConstructor( + raw.ccId, + raw.ccCommand, + ); + } + // Not every CC has a constructor for its commands. In that case, + // call the CC constructor directly + try { + return (CommandConstructor ?? CCConstructor).from(raw, ctx); + } catch (e) { + // Indicate invalid payloads with a special CC type + if ( + isZWaveError(e) + && e.code === ZWaveErrorCodes.PacketFormat_InvalidPayload + ) { + const ccName = CommandConstructor?.name + ?? `${getCCName(raw.ccId)} CC`; - 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) { + // Preserve why the command was invalid + let reason: string | ZWaveErrorCodes | undefined; if ( - isZWaveError(e) - && e.code === ZWaveErrorCodes.CC_NotImplemented + typeof e.context === "string" + || (typeof e.context === "number" + && ZWaveErrorCodes[e.context] != undefined) ) { - // 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; + reason = e.context; } - } - // 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; + const ret = new InvalidCC({ + nodeId: ctx.sourceNodeId, + ccId: raw.ccId, + ccCommand: raw.ccCommand, + ccName, + reason, + }); + + return ret; + } + throw e; } } - protected host: ZWaveHost; + public static from(raw: CCRaw, ctx: CCParsingContext): CommandClass { + return new this({ + nodeId: ctx.sourceNodeId, + ccId: raw.ccId, + ccCommand: raw.ccCommand, + payload: raw.payload, + }); + } /** This CC's identifier */ public ccId!: CommandClasses; @@ -237,13 +291,6 @@ 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; @@ -278,7 +325,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 +335,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( @@ -301,33 +348,11 @@ export class CommandClass implements ICommandClass { ); } - /** - * Deserializes a CC from a buffer that contains a serialized CC - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - protected deserialize(data: Buffer) { - const ccId = CommandClass.getCommandClass(data); - const ccIdLength = this.isExtended() ? 2 : 1; - if (data.length > ccIdLength) { - // This is not a NoOp CC (contains command and payload) - const ccCommand = data[ccIdLength]; - const payload = data.subarray(ccIdLength + 1); - return { - ccId, - ccCommand, - payload, - }; - } else { - // NoOp CC (no command, no payload) - const payload = Buffer.allocUnsafe(0); - return { ccId, payload }; - } - } - /** * 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]); @@ -353,94 +378,6 @@ export class CommandClass implements ICommandClass { // Do nothing by default } - /** Extracts the CC id from a buffer that contains a serialized CC */ - public static getCommandClass(data: Buffer): CommandClasses { - return parseCCId(data).ccId; - } - - /** Extracts the CC command from a buffer that contains a serialized CC */ - public static getCCCommand(data: Buffer): number | undefined { - if (data[0] === 0) return undefined; // NoOp - const isExtendedCC = data[0] >= 0xf1; - return isExtendedCC ? data[2] : data[1]; - } - - /** - * Retrieves the correct constructor for the CommandClass in the given Buffer. - * It is assumed that the buffer only contains the serialized CC. This throws if the CC is not implemented. - */ - public static getConstructor(ccData: Buffer): CCConstructor { - const cc = CommandClass.getCommandClass(ccData); - const ret = getCCConstructor(cc); - if (!ret) { - const ccName = getCCName(cc); - throw new ZWaveError( - `The command class ${ccName} is not implemented`, - ZWaveErrorCodes.CC_NotImplemented, - ); - } - return ret; - } - - /** - * 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 - const ccId = CommandClass.getCommandClass(options.data); - const Constructor = getCCConstructor(ccId) ?? CommandClass; - - try { - return new Constructor(host, options); - } catch (e) { - // Indicate invalid payloads with a special CC type - if ( - isZWaveError(e) - && e.code === ZWaveErrorCodes.PacketFormat_InvalidPayload - ) { - const nodeId = options.fromEncapsulation - ? options.encapCC.nodeId - : options.nodeId; - let ccName: string | undefined; - const ccId = CommandClass.getCommandClass(options.data); - const ccCommand = CommandClass.getCCCommand(options.data); - if (ccCommand != undefined) { - ccName = getCCCommandConstructor(ccId, ccCommand)?.name; - } - // Fall back to the unspecified CC if the command cannot be determined - if (!ccName) { - ccName = `${getCCName(ccId)} CC`; - } - // Preserve why the command was invalid - let reason: string | ZWaveErrorCodes | undefined; - if ( - typeof e.context === "string" - || (typeof e.context === "number" - && ZWaveErrorCodes[e.context] != undefined) - ) { - reason = e.context; - } - - const ret = new InvalidCC(host, { - nodeId, - ccId, - ccName, - reason, - }); - - if (options.fromEncapsulation) { - ret.encapsulatingCC = options.encapCC as any; - } - - return ret; - } - throw e; - } - } - /** * Create an instance of the given CC without checking whether it is supported. * If the CC is implemented, this returns an instance of the given CC which is linked to the given endpoint. @@ -448,21 +385,20 @@ 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, + endpointIndex: endpoint.index, }) as T; } } /** 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 +450,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 +467,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 +505,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 +547,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 +559,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 +574,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 +604,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 +620,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 +634,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 +648,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 +661,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 +675,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 +688,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 +701,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 +742,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 +788,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 +817,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 +826,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 +876,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 +902,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 +916,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 +952,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 +1040,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 +1054,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 { @@ -1162,14 +1139,14 @@ export class CommandClass implements ICommandClass { } } -export interface InvalidCCCreationOptions extends CommandClassCreationOptions { +export interface InvalidCCOptions extends CommandClassOptions { ccName: string; reason?: string | ZWaveErrorCodes; } export class InvalidCC extends CommandClass { - public constructor(host: ZWaveHost, options: InvalidCCCreationOptions) { - super(host, options); + public constructor(options: InvalidCCOptions) { + super(options); this._ccName = options.ccName; // Numeric reasons are used internally to communicate problems with a CC // without ignoring them entirely @@ -1182,7 +1159,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 @@ -1199,29 +1176,9 @@ export class InvalidCC extends CommandClass { } } -/** @publicAPI */ -export function assertValidCCs(container: ICommandClassContainer): void { - if (container.command instanceof InvalidCC) { - if (typeof container.command.reason === "number") { - throw new ZWaveError( - "The message payload failed validation!", - container.command.reason, - ); - } else { - throw new ZWaveError( - "The message payload is invalid!", - ZWaveErrorCodes.PacketFormat_InvalidPayload, - container.command.reason, - ); - } - } else if (isCommandClassContainer(container.command)) { - assertValidCCs(container.command); - } -} - 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/ICommandClassContainer.ts b/packages/cc/src/lib/ICommandClassContainer.ts index 2bdbf4ff8f82..e69de29bb2d1 100644 --- a/packages/cc/src/lib/ICommandClassContainer.ts +++ b/packages/cc/src/lib/ICommandClassContainer.ts @@ -1,14 +0,0 @@ -import { CommandClass } from "./CommandClass"; - -export interface ICommandClassContainer { - command: CommandClass; -} - -/** - * Tests if the given message contains a CC - */ -export function isCommandClassContainer( - msg: T | undefined, -): msg is T & ICommandClassContainer { - return (msg as any)?.command instanceof CommandClass; -} 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/cc/tsconfig.build.json b/packages/cc/tsconfig.build.json index ce700310e0a9..1105b5ff1f35 100644 --- a/packages/cc/tsconfig.build.json +++ b/packages/cc/tsconfig.build.json @@ -14,9 +14,6 @@ { "path": "../host/tsconfig.build.json" }, - { - "path": "../serial/tsconfig.build.json" - }, { "path": "../shared/tsconfig.build.json" }, diff --git a/packages/core/core.api.md b/packages/core/core.api.md index 95ac25d9f992..8ba366234706 100644 --- a/packages/core/core.api.md +++ b/packages/core/core.api.md @@ -108,7 +108,7 @@ export enum BeamingInfo { // Warning: (ae-missing-release-tag) "BroadcastCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type BroadcastCC = T & { +export type BroadcastCC = T & { nodeId: typeof NODE_ID_BROADCAST | typeof NODE_ID_BROADCAST_LR; }; @@ -168,6 +168,26 @@ export interface CacheValue extends Pick & ValueLogConte internal?: boolean; }; +// Warning: (ae-missing-release-tag) "ControlsCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ControlsCC { + // (undocumented) + controlsCC(cc: CommandClasses): boolean; +} + // Warning: (ae-missing-release-tag) "CRC16_CCITT" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -996,6 +1024,18 @@ export function encryptAES128ECB(plaintext: Buffer, key: Buffer): Buffer; // @public export const encryptAES128OFB: (input: Buffer, key: Buffer, iv: Buffer) => Buffer; +// Warning: (ae-missing-release-tag) "EndpointId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface EndpointId { + // (undocumented) + readonly index: number; + // (undocumented) + readonly nodeId: number; + // (undocumented) + readonly virtual: false; +} + // Warning: (ae-missing-release-tag) "enumValuesToMetadataStates" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1108,6 +1148,14 @@ export interface GenericDeviceClassWithSpecific extends GenericDeviceClass { // @public export function getAllDeviceClasses(): readonly GenericDeviceClassWithSpecific[]; +// Warning: (ae-missing-release-tag) "GetAllEndpoints" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetAllEndpoints { + // (undocumented) + getAllEndpoints(): T[]; +} + // Warning: (ae-missing-release-tag) "getAllIndicatorProperties" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1153,6 +1201,14 @@ export function getBitMaskWidth(mask: number): number; // @public (undocumented) export function getCCName(cc: number): string; +// Warning: (ae-missing-release-tag) "GetCCs" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetCCs { + // (undocumented) + getCCs(): Iterable<[ccId: CommandClasses, info: CommandClassInfo]>; +} + // Warning: (ae-missing-release-tag) "getChipTypeAndVersion" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1176,6 +1232,18 @@ export function getDirectionPrefix(direction: DataDirection): "« " | "» " | " // @public export function getDSTInfo(now?: Date): DSTInfo; +// Warning: (ae-missing-release-tag) "GetEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetEndpoint { + // (undocumented) + getEndpoint(index: 0): T; + // (undocumented) + getEndpoint(index: number): T | undefined; + // (undocumented) + getEndpointOrThrow(index: number): T; +} + // Warning: (ae-missing-release-tag) "getErrorSuffix" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1355,24 +1423,6 @@ export function highResTimestamp(): number; // @public export const HOMEID_BYTES = 4; -// Warning: (ae-missing-release-tag) "ICommandClass" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface ICommandClass { - // (undocumented) - ccCommand?: number; - // (undocumented) - ccId: CommandClasses; - // (undocumented) - expectsCCResponse(): boolean; - // (undocumented) - isExpectedCCResponse(received: ICommandClass): boolean; - // (undocumented) - nodeId: number | MulticastDestination; - // (undocumented) - serialize(): Buffer; -} - // Warning: (ae-missing-release-tag) "importRawECDHPrivateKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1720,6 +1770,14 @@ export function isActuatorCC(cc: CommandClasses): boolean; // @public export function isApplicationCC(cc: CommandClasses): boolean; +// Warning: (ae-missing-release-tag) "IsCCSecure" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface IsCCSecure { + // (undocumented) + isCCSecure(cc: CommandClasses): boolean; +} + // Warning: (ae-missing-release-tag) "isConsecutiveArray" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -1830,96 +1888,6 @@ export function isValueID(param: Record): param is ValueID; // @public (undocumented) export function isZWaveError(e: unknown): e is ZWaveError; -// Warning: (ae-missing-release-tag) "IVirtualEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface IVirtualEndpoint { - // (undocumented) - getCCVersion(cc: CommandClasses): number; - // (undocumented) - readonly index: number; - // (undocumented) - readonly node: IVirtualNode; - // (undocumented) - readonly nodeId: number | MulticastDestination; - // (undocumented) - supportsCC(cc: CommandClasses): boolean; - // (undocumented) - readonly virtual: true; -} - -// Warning: (ae-missing-release-tag) "IVirtualNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface IVirtualNode extends IVirtualEndpoint { - // (undocumented) - getEndpoint(index: 0): IVirtualEndpoint; - // (undocumented) - getEndpoint(index: number): IVirtualEndpoint | undefined; - // (undocumented) - getEndpointOrThrow(index: number): IVirtualEndpoint; - // (undocumented) - readonly id: number | undefined; - // (undocumented) - readonly physicalNodes: readonly IZWaveNode[]; -} - -// Warning: (ae-missing-release-tag) "IZWaveEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface IZWaveEndpoint { - // (undocumented) - addCC(cc: CommandClasses, info: Partial): void; - // (undocumented) - controlsCC(cc: CommandClasses): boolean; - // (undocumented) - getCCs(): Iterable<[ccId: CommandClasses, info: CommandClassInfo]>; - // (undocumented) - getCCVersion(cc: CommandClasses): number; - // (undocumented) - getNodeUnsafe(): IZWaveNode | undefined; - // (undocumented) - readonly index: number; - // (undocumented) - isCCSecure(cc: CommandClasses): boolean; - // (undocumented) - readonly nodeId: number; - // (undocumented) - removeCC(cc: CommandClasses): void; - // (undocumented) - supportsCC(cc: CommandClasses): boolean; - // (undocumented) - readonly virtual: false; -} - -// Warning: (ae-missing-release-tag) "IZWaveNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface IZWaveNode extends IZWaveEndpoint, SecurityClassOwner { - // (undocumented) - readonly canSleep: MaybeNotKnown; - // (undocumented) - getAllEndpoints(): IZWaveEndpoint[]; - // (undocumented) - getEndpoint(index: 0): IZWaveEndpoint; - // (undocumented) - getEndpoint(index: number): IZWaveEndpoint | undefined; - // (undocumented) - getEndpointOrThrow(index: number): IZWaveEndpoint; - // (undocumented) - readonly id: number; - // (undocumented) - interviewStage: InterviewStage; - // (undocumented) - isFrequentListening: MaybeNotKnown; - // (undocumented) - isListening: MaybeNotKnown; - // (undocumented) - readonly isSecure: MaybeNotKnown; - // (undocumented) - readonly status: NodeStatus; -} - // Warning: (ae-missing-release-tag) "KeyPair" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1935,6 +1903,15 @@ export interface KeyPair { // @public (undocumented) export function keyPairFromRawECDHPrivateKey(privateKey: Buffer): KeyPair; +// Warning: (ae-missing-release-tag) "ListenBehavior" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ListenBehavior { + readonly canSleep: MaybeNotKnown; + readonly isFrequentListening: MaybeNotKnown; + readonly isListening: MaybeNotKnown; +} + // Warning: (ae-missing-release-tag) "LOG_PREFIX_WIDTH" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -2274,6 +2251,16 @@ export interface MeterScaleDefinition { // @public (undocumented) export type MeterScaleGroup = Record; +// Warning: (ae-missing-release-tag) "ModifyCCs" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ModifyCCs { + // (undocumented) + addCC(cc: CommandClasses, info: Partial): void; + // (undocumented) + removeCC(cc: CommandClasses): void; +} + // Warning: (ae-missing-release-tag) "MPANState" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2312,7 +2299,7 @@ export enum MPDUHeaderType { // Warning: (ae-missing-release-tag) "MulticastCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type MulticastCC = T & { +export type MulticastCC = T & { nodeId: MulticastDestination; }; @@ -2364,6 +2351,14 @@ export const NODE_ID_BROADCAST_LR = 4095; // @public export const NODE_ID_MAX = 232; +// Warning: (ae-missing-release-tag) "NodeId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface NodeId extends EndpointId { + // (undocumented) + readonly id: number; +} + // Warning: (ae-missing-release-tag) "NodeIDType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2729,6 +2724,14 @@ export function parsePartial(value: number, bitMask: number, signed: boolean): n // @public export function parseQRCodeString(qr: string): QRProvisioningInformation; +// Warning: (ae-missing-release-tag) "PhysicalNodes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface PhysicalNodes { + // (undocumented) + readonly physicalNodes: readonly T[]; +} + // Warning: (ae-missing-release-tag) "ProtocolDataRate" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2887,6 +2890,22 @@ export type QRProvisioningInformation = { dsk: string; } & ProvisioningInformation_ProductType & ProvisioningInformation_ProductId & Partial & Partial & Partial; +// Warning: (ae-missing-release-tag) "QueryNodeStatus" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface QueryNodeStatus { + readonly status: NodeStatus; +} + +// Warning: (ae-missing-release-tag) "QuerySecurityClasses" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface QuerySecurityClasses { + getHighestSecurityClass(): MaybeNotKnown; + hasSecurityClass(securityClass: SecurityClass): MaybeNotKnown; + readonly isSecure: MaybeNotKnown; +} + // Warning: (ae-missing-release-tag) "ReflectionDecorator" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3118,20 +3137,6 @@ export function securityClassIsS2(secClass: SecurityClass | undefined): secClass // @public export const securityClassOrder: readonly [SecurityClass.S2_AccessControl, SecurityClass.S2_Authenticated, SecurityClass.S2_Unauthenticated, SecurityClass.S0_Legacy]; -// Warning: (ae-missing-release-tag) "SecurityClassOwner" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface SecurityClassOwner { - // (undocumented) - getHighestSecurityClass(): MaybeNotKnown; - // (undocumented) - hasSecurityClass(securityClass: SecurityClass): MaybeNotKnown; - // (undocumented) - readonly id: number; - // (undocumented) - setSecurityClass(securityClass: SecurityClass, granted: boolean): void; -} - // Warning: (ae-missing-release-tag) "SecurityManager" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3232,6 +3237,15 @@ export interface SecurityManagerOptions { ownNodeId: number; } +// Warning: (ae-missing-release-tag) "SecurityManagers" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SecurityManagers { + securityManager: SecurityManager | undefined; + securityManager2: SecurityManager2 | undefined; + securityManagerLR: SecurityManager2 | undefined; +} + // Warning: (ae-missing-release-tag) "SendCommandOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3246,7 +3260,7 @@ export type SendCommandOptions = SendMessageOptions & SupervisionOptions & SendC // Warning: (ae-missing-release-tag) "SendCommandReturnType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type SendCommandReturnType = undefined extends TResponse ? SupervisionResult | undefined : TResponse | undefined; +export type SendCommandReturnType = undefined extends TResponse ? SupervisionResult | undefined : TResponse | undefined; // Warning: (ae-missing-release-tag) "SendCommandSecurityS2Options" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -3349,6 +3363,14 @@ export interface SetNonceOptions { free?: boolean; } +// Warning: (ae-missing-release-tag) "SetSecurityClass" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SetSecurityClass { + // (undocumented) + setSecurityClass(securityClass: SecurityClass, granted: boolean): void; +} + // Warning: (ae-missing-release-tag) "SetValueOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3373,7 +3395,7 @@ export interface SimpleReflectionDecorator = T & { +export type SinglecastCC = T & { nodeId: number; }; @@ -3482,6 +3504,16 @@ export enum SupervisionStatus { // @public (undocumented) export type SupervisionUpdateHandler = (update: SupervisionResult) => void; +// Warning: (ae-missing-release-tag) "SupportsCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SupportsCC { + // (undocumented) + getCCVersion(cc: CommandClasses): number; + // (undocumented) + supportsCC(cc: CommandClasses): boolean; +} + // Warning: (ae-missing-release-tag) "tagify" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -3647,7 +3679,6 @@ export interface TXReport { failedRouteFirstNonFunctionalNodeId?: number; failedRouteLastFunctionalNodeId?: number; measuredNoiseFloor?: RSSI; - numRepeaters: number; repeaterNodeIds: [number?, number?, number?, number?]; routeSchemeState: RoutingScheme; routeSpeed: ProtocolDataRate; @@ -4183,6 +4214,26 @@ export interface ValueUpdatedArgs extends ValueID { source?: "driver" | "node"; } +// Warning: (ae-missing-release-tag) "VirtualEndpointId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface VirtualEndpointId { + // (undocumented) + readonly index: number; + // (undocumented) + readonly nodeId: number | MulticastDestination; + // (undocumented) + readonly virtual: true; +} + +// Warning: (ae-missing-release-tag) "VirtualNodeId" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface VirtualNodeId extends VirtualEndpointId { + // (undocumented) + readonly id: number | undefined; +} + // Warning: (ae-missing-release-tag) "wasControllerReset" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -4190,6 +4241,11 @@ export function wasControllerReset(e: unknown): e is ZWaveError & { code: ZWaveErrorCodes.Controller_Reset; }; +// Warning: (ae-missing-release-tag) "WithAddress" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WithAddress = T & CCAddress; + // Warning: (ae-missing-release-tag) "ZnifferProtocolDataRate" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -4331,11 +4387,11 @@ export function zwaveDataRateToString(rate: ZWaveDataRate): string; // @public export class ZWaveError extends Error { constructor(message: string, code: ZWaveErrorCodes, - context?: unknown | undefined, + context?: unknown, transactionSource?: string | undefined); // (undocumented) readonly code: ZWaveErrorCodes; - readonly context?: unknown | undefined; + readonly context?: unknown; // (undocumented) readonly message: string; readonly transactionSource?: string | undefined; diff --git a/packages/core/package.json b/packages/core/package.json index cbafa3c49dea..117b949f4329 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,6 +65,7 @@ "logform": "^2.6.1", "nrf-intel-hex": "^1.4.0", "reflect-metadata": "^0.2.2", + "semver": "^7.6.3", "triple-beam": "*", "winston": "^3.15.0", "winston-daily-rotate-file": "^5.0.0", @@ -73,6 +74,7 @@ "devDependencies": { "@microsoft/api-extractor": "^7.47.9", "@types/node": "^18.19.55", + "@types/semver": "^7.5.8", "@types/sinon": "^17.0.3", "@types/triple-beam": "^1.3.5", "ava": "^6.1.3", 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..ba8c07ac2ca8 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) */ @@ -163,8 +163,6 @@ export function routingSchemeToString(scheme: RoutingScheme): string { export interface TXReport { /** Transmission time in ticks (multiples of 10ms) */ txTicks: number; - /** Number of repeaters used in the route to the destination, 0 for direct range */ - numRepeaters: number; /** RSSI value of the acknowledgement frame */ ackRSSI?: RSSI; /** RSSI values of the incoming acknowledgement frame, measured by repeater 0...3 */ @@ -297,7 +295,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..32ad022aa963 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,7 +27,12 @@ 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/compareVersions"; export * from "./util/config"; export * from "./util/crc"; export * from "./util/date"; 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..924ab874dcdb --- /dev/null +++ b/packages/core/src/traits/CommandClasses.ts @@ -0,0 +1,62 @@ +import type { + CommandClassInfo, + CommandClasses, +} from "../capabilities/CommandClasses"; +import type { + MulticastDestination, + NODE_ID_BROADCAST, + NODE_ID_BROADCAST_LR, +} from "../consts"; + +/** Identifies which node and/or endpoint a CC is addressed to */ +export interface CCAddress { + nodeId: number | MulticastDestination; + endpointIndex?: number; +} + +export type WithAddress = T & CCAddress; + +/** Uniquely identifies a CC and its address */ +export interface CCId extends CCAddress { + 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/core/src/util/compareVersions.ts b/packages/core/src/util/compareVersions.ts new file mode 100644 index 000000000000..9315d0e05b24 --- /dev/null +++ b/packages/core/src/util/compareVersions.ts @@ -0,0 +1,47 @@ +import { padVersion } from "@zwave-js/shared"; +import semver from "semver"; +import { type MaybeNotKnown } from "../values/Primitive"; + +/** Checks if the SDK version is greater than the given one */ +export function sdkVersionGt( + sdkVersion: MaybeNotKnown, + compareVersion: string, +): MaybeNotKnown { + if (sdkVersion === undefined) { + return undefined; + } + return semver.gt(padVersion(sdkVersion), padVersion(compareVersion)); +} + +/** Checks if the SDK version is greater than or equal to the given one */ +export function sdkVersionGte( + sdkVersion: MaybeNotKnown, + compareVersion: string, +): MaybeNotKnown { + if (sdkVersion === undefined) { + return undefined; + } + return semver.gte(padVersion(sdkVersion), padVersion(compareVersion)); +} + +/** Checks if the SDK version is lower than the given one */ +export function sdkVersionLt( + sdkVersion: MaybeNotKnown, + compareVersion: string, +): MaybeNotKnown { + if (sdkVersion === undefined) { + return undefined; + } + return semver.lt(padVersion(sdkVersion), padVersion(compareVersion)); +} + +/** Checks if the SDK version is lower than or equal to the given one */ +export function sdkVersionLte( + sdkVersion: MaybeNotKnown, + compareVersion: string, +): MaybeNotKnown { + if (sdkVersion === undefined) { + return undefined; + } + return semver.lte(padVersion(sdkVersion), padVersion(compareVersion)); +} diff --git a/packages/eslint-plugin/src/rules/consistent-cc-classes.ts b/packages/eslint-plugin/src/rules/consistent-cc-classes.ts index 2db8230635ae..b62927689f91 100644 --- a/packages/eslint-plugin/src/rules/consistent-cc-classes.ts +++ b/packages/eslint-plugin/src/rules/consistent-cc-classes.ts @@ -45,6 +45,9 @@ function getRequiredInterviewCCsFromMethod( export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({ create(context) { let currentCCId: CommandClasses | undefined; + let isInCCCommand = false; + let ctor: TSESTree.MethodDefinition | undefined; + let hasFromImpl: boolean; return { // Look at class declarations ending with "CC" @@ -154,7 +157,23 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({ } }, MethodDefinition(node) { - // Only care about methods inside non-application CC classes, + if (isInCCCommand) { + if ( + node.key.type === AST_NODE_TYPES.Identifier + && node.key.name === "from" + ) { + hasFromImpl = true; + } + + if ( + node.key.type === AST_NODE_TYPES.Identifier + && node.key.name === "constructor" + ) { + ctor = node; + } + } + + // For the following, only care about methods inside non-application CC classes, // since only application CCs may depend on other application CCs if (!currentCCId || applicationCCs.includes(currentCCId)) { return; @@ -198,8 +217,64 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({ }); } }, - "ClassDeclaration:exit"(_node) { + + "ClassDeclaration:exit"(node) { + // Ensure each CC class with a custom constructor also has a from method + if (isInCCCommand && !!ctor && !hasFromImpl) { + const fix = (fixer: TSESLint.RuleFixer) => { + return fixer.insertTextAfter( + ctor!, + ` + + public static from( + raw: CCRaw, + ctx: CCParsingContext, + ): ${node.id!.name} { + // TODO: Deserialize payload + throw new ZWaveError( + \`\${this.name}: deserialization not implemented\`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + }`, + ); + }; + + context.report({ + node: ctor, + loc: ctor.key.loc, + messageId: "missing-from-impl", + suggest: [ + { + messageId: "suggest-impl-from", + fix, + }, + ], + }); + } + currentCCId = undefined; + isInCCCommand = false; + hasFromImpl = false; + ctor = undefined; + }, + + // ================================================================= + + // Ensure consistent implementation of CC commands + + // Look at class declarations containing, but not ending with "CC" + "ClassDeclaration[id.name=/.+CC.+/]"( + node: TSESTree.ClassDeclaration & { + id: TSESTree.Identifier; + }, + ) { + if ( + node.superClass?.type === AST_NODE_TYPES.Identifier + && node.superClass.name.endsWith("CC") + ) { + // TODO: Implement more rules, for now only look at constructor/from + isInCCCommand = true; + } }, // ================================================================= @@ -330,12 +405,15 @@ export const consistentCCClasses = ESLintUtils.RuleCreator.withoutDocs({ "Classes implementing a CC API must have a CC assigned using the `@API(...)` decorator", "missing-version-decorator": "Classes implementing a CC must be decorated with `@implementedVersion(...)`", + "missing-from-impl": + "CC implementations with a custom constructor must also override the `CommandClass.from(...)` method", "must-export": "Classes implementing a CC must be exported", "must-export-api": "Classes implementing a CC API must be exported", "must-inherit-ccapi": "Classes implementing a CC API MUST inherit from `CCAPI` or `PhysicalCCAPI`", "suggest-extend-ccapi": "Inherit from `CCAPI`", "suggest-extend-physicalccapi": "Inherit from `PhysicalCCAPI`", + "suggest-impl-from": "Override `CommandClass.from(...)`", "must-inherit-commandclass": "Classes implementing a CC MUST inherit from `CommandClass`", "required-ccs-failed": diff --git a/packages/eslint-plugin/src/rules/no-internal-cc-types.ts b/packages/eslint-plugin/src/rules/no-internal-cc-types.ts index 0a148b853903..9742416528a8 100644 --- a/packages/eslint-plugin/src/rules/no-internal-cc-types.ts +++ b/packages/eslint-plugin/src/rules/no-internal-cc-types.ts @@ -31,7 +31,6 @@ export const noInternalCCTypes = ESLintUtils.RuleCreator.withoutDocs({ | TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeAliasDeclaration, ) { - if (node.id.name === "BasicCCSetOptions") debugger; let fullNode: | TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeAliasDeclaration diff --git a/packages/host/host.api.md b/packages/host/host.api.md index 202074c98e5f..49990afb56b5 100644 --- a/packages/host/host.api.md +++ b/packages/host/host.api.md @@ -4,42 +4,76 @@ ```ts +import type { CCId } from '@zwave-js/core'; import type { CommandClasses } from '@zwave-js/core'; -import type { ConfigManager } from '@zwave-js/config'; import type { ControllerLogger } from '@zwave-js/core'; +import { ControlsCC } from '@zwave-js/core'; import type { DeviceConfig } from '@zwave-js/config'; -import type { ICommandClass } from '@zwave-js/core'; -import { IZWaveNode } from '@zwave-js/core'; +import { EndpointId } from '@zwave-js/core'; +import type { FrameType } from '@zwave-js/core'; +import { GetEndpoint } from '@zwave-js/core'; +import { IsCCSecure } from '@zwave-js/core'; +import { ListenBehavior } from '@zwave-js/core'; import type { MaybeNotKnown } from '@zwave-js/core'; -import type { NodeIDType } from '@zwave-js/core'; -import type { Overwrite } from 'alcalzone-shared/types'; -import type { ReadonlyThrowingMap } from '@zwave-js/shared'; +import { NodeId } from '@zwave-js/core'; +import { QuerySecurityClasses } from '@zwave-js/core'; import type { SecurityClass } from '@zwave-js/core'; -import type { SecurityManager } from '@zwave-js/core'; -import type { SecurityManager2 } from '@zwave-js/core'; +import type { SecurityManagers } from '@zwave-js/core'; import type { SendCommandOptions } from '@zwave-js/core'; import type { SendCommandReturnType } from '@zwave-js/core'; -import { ThrowingMap } from '@zwave-js/shared'; +import { SetSecurityClass } from '@zwave-js/core'; +import { SupportsCC } from '@zwave-js/core'; import type { ValueDB } from '@zwave-js/core'; import type { ValueID } from '@zwave-js/core'; -// Warning: (ae-missing-release-tag) "createTestingHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "BaseTestEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public -export function createTestingHost(options?: Partial): TestingHost; +// @public (undocumented) +export type BaseTestEndpoint = EndpointId & SupportsCC & ControlsCC & IsCCSecure; -// Warning: (ae-missing-release-tag) "CreateTestingHostOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "BaseTestNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface CreateTestingHostOptions { +export type BaseTestNode = BaseTestEndpoint & NodeId & ListenBehavior & QuerySecurityClasses & SetSecurityClass & SupportsCC & ControlsCC & IsCCSecure & GetEndpoint; + +// Warning: (ae-missing-release-tag) "CCEncodingContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface CCEncodingContext extends Readonly, GetDeviceConfig, HostIDs, GetSupportedCCVersion { + // (undocumented) + getHighestSecurityClass(nodeId: number): MaybeNotKnown; + // (undocumented) + hasSecurityClass(nodeId: number, securityClass: SecurityClass): MaybeNotKnown; + // (undocumented) + setSecurityClass(nodeId: number, securityClass: SecurityClass, granted: boolean): void; +} + +// Warning: (ae-missing-release-tag) "CCParsingContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface CCParsingContext extends Readonly, GetDeviceConfig, HostIDs { + // (undocumented) + __internalIsMockNode?: boolean; + frameType: FrameType; // (undocumented) - getSafeCCVersion: ZWaveHost["getSafeCCVersion"]; + getHighestSecurityClass(nodeId: number): MaybeNotKnown; // (undocumented) - getSupportedCCVersion?: ZWaveHost["getSupportedCCVersion"]; + hasSecurityClass(nodeId: number, securityClass: SecurityClass): MaybeNotKnown; // (undocumented) - homeId: ZWaveHost["homeId"]; + setSecurityClass(nodeId: number, securityClass: SecurityClass, granted: boolean): void; // (undocumented) - ownNodeId: ZWaveHost["ownNodeId"]; + sourceNodeId: number; +} + +// Warning: (ae-missing-release-tag) "createTestingHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function createTestingHost(options?: Partial): TestingHost; + +// Warning: (ae-missing-release-tag) "CreateTestingHostOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CreateTestingHostOptions extends HostIDs, GetDeviceConfig { } // Warning: (ae-missing-release-tag) "FileSystem" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -58,65 +92,128 @@ export interface FileSystem { } | BufferEncoding): Promise; } -// Warning: (ae-missing-release-tag) "NodeSchedulePollOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GetAllNodes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) -export interface NodeSchedulePollOptions { - expectedValue?: unknown; - timeoutMs?: number; +// @public +export interface GetAllNodes { + // (undocumented) + getAllNodes(): T[]; } -// Warning: (ae-missing-release-tag) "TestingHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GetCommunicationTimeouts" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) -export type TestingHost = Overwrite, { - nodes: ThrowingMap; -}>; +// @public +export interface GetCommunicationTimeouts { + // (undocumented) + getCommunicationTimeouts(): ZWaveHostOptions["timeouts"]; +} -// Warning: (ae-missing-release-tag) "ZWaveApplicationHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GetDeviceConfig" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ZWaveApplicationHost extends ZWaveValueHost, ZWaveHost { - configManager: ConfigManager; +export interface GetDeviceConfig { // (undocumented) - controllerLog: ControllerLogger; - isControllerNode(nodeId: number): boolean; - nodes: ReadonlyThrowingMap; - // (undocumented) - options: ZWaveHostOptions; - // (undocumented) - schedulePoll(nodeId: number, valueId: ValueID, options: NodeSchedulePollOptions): boolean; - // (undocumented) - sendCommand(command: ICommandClass, options?: SendCommandOptions): Promise>; - // (undocumented) - waitForCommand(predicate: (cc: ICommandClass) => boolean, timeout: number): Promise; + getDeviceConfig(nodeId: number): DeviceConfig | undefined; } -// Warning: (ae-missing-release-tag) "ZWaveHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GetInterviewOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ZWaveHost { +export interface GetInterviewOptions { // (undocumented) - __internalIsMockNode?: boolean; + getInterviewOptions(): ZWaveHostOptions["interview"]; +} + +// Warning: (ae-missing-release-tag) "GetNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetNode { // (undocumented) - getDeviceConfig?: (nodeId: number) => DeviceConfig | undefined; + getNode(nodeId: number): T | undefined; // (undocumented) - getHighestSecurityClass(nodeId: number): MaybeNotKnown; - getNextCallbackId(): number; - getNextSupervisionSessionId(nodeId: number): number; - getSafeCCVersion(cc: CommandClasses, nodeId: number, endpointIndex?: number): number; + getNodeOrThrow(nodeId: number): T; +} + +// Warning: (ae-missing-release-tag) "GetSafeCCVersion" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GetSafeCCVersion { + getSafeCCVersion(cc: CommandClasses, nodeId: number, endpointIndex?: number): number | undefined; +} + +// Warning: (ae-missing-release-tag) "GetSupportedCCVersion" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GetSupportedCCVersion { getSupportedCCVersion(cc: CommandClasses, nodeId: number, endpointIndex?: number): number; +} + +// Warning: (ae-missing-release-tag) "GetUserPreferences" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetUserPreferences { // (undocumented) - hasSecurityClass(nodeId: number, securityClass: SecurityClass): MaybeNotKnown; + getUserPreferences(): ZWaveHostOptions["preferences"]; +} + +// Warning: (ae-missing-release-tag) "GetValueDB" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetValueDB { + getValueDB(nodeId: number): ValueDB; + tryGetValueDB(nodeId: number): ValueDB | undefined; +} + +// Warning: (ae-missing-release-tag) "HostIDs" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface HostIDs { homeId: number; - isCCSecure(cc: CommandClasses, nodeId: number, endpointIndex?: number): boolean; - readonly nodeIdType?: NodeIDType; ownNodeId: number; - securityManager: SecurityManager | undefined; - securityManager2: SecurityManager2 | undefined; - securityManagerLR: SecurityManager2 | undefined; +} + +// Warning: (ae-missing-release-tag) "LogNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LogNode = Pick; + +// Warning: (ae-missing-release-tag) "LookupManufacturer" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface LookupManufacturer { + lookupManufacturer(manufacturerId: number): string | undefined; +} + +// Warning: (ae-missing-release-tag) "NodeSchedulePollOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface NodeSchedulePollOptions { + expectedValue?: unknown; + timeoutMs?: number; +} + +// Warning: (ae-missing-release-tag) "SchedulePoll" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SchedulePoll { // (undocumented) - setSecurityClass(nodeId: number, securityClass: SecurityClass, granted: boolean): void; + schedulePoll(nodeId: number, valueId: ValueID, options: NodeSchedulePollOptions): boolean; +} + +// Warning: (ae-missing-release-tag) "SendCommand" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SendCommand { + // (undocumented) + sendCommand(command: CCId, options?: SendCommandOptions): Promise>; +} + +// Warning: (ae-missing-release-tag) "TestingHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface TestingHost extends HostIDs, GetValueDB, GetSupportedCCVersion, GetAllNodes, GetNode, GetDeviceConfig, LogNode { + // (undocumented) + setNode(nodeId: number, node: BaseTestNode): void; } // Warning: (ae-missing-release-tag) "ZWaveHostOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -144,14 +241,6 @@ export interface ZWaveHostOptions { }; } -// Warning: (ae-missing-release-tag) "ZWaveValueHost" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface ZWaveValueHost { - getValueDB(nodeId: number): ValueDB; - tryGetValueDB(nodeId: number): ValueDB | undefined; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/host/src/ZWaveHost.ts b/packages/host/src/ZWaveHost.ts index 23de183ef77e..ad60c0001502 100644 --- a/packages/host/src/ZWaveHost.ts +++ b/packages/host/src/ZWaveHost.ts @@ -1,50 +1,34 @@ -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; - - /** - * 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. - */ - getSafeCCVersion( - cc: CommandClasses, - nodeId: number, - endpointIndex?: number, - ): 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 +38,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 +75,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 +109,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/src/generateTypedDocs.ts b/packages/maintenance/src/generateTypedDocs.ts index ae4ec6d61661..46fb2d8d2474 100644 --- a/packages/maintenance/src/generateTypedDocs.ts +++ b/packages/maintenance/src/generateTypedDocs.ts @@ -359,9 +359,8 @@ async function processCCDocFile( if (!APIClass) return; const ccId = getCommandClassFromClassDeclaration( - // FIXME: there seems to be some discrepancy between ts-morph's bundled typescript and our typescript - file.compilerNode as any, - APIClass.compilerNode as any, + file.compilerNode, + APIClass.compilerNode, ); if (ccId == undefined) return; const ccName = getCCName(ccId); diff --git a/packages/maintenance/src/refactorCCParsing.01.ts b/packages/maintenance/src/refactorCCParsing.01.ts new file mode 100644 index 000000000000..2922a7118737 --- /dev/null +++ b/packages/maintenance/src/refactorCCParsing.01.ts @@ -0,0 +1,319 @@ +import fs from "node:fs/promises"; +import { + type Node, + Project, + SyntaxKind, + type TypeReferenceNode, + VariableDeclarationKind, +} from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/cc/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getBaseNameWithoutExtension().endsWith("CC") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + const ifaceExtends = file.getDescendantsOfKind( + SyntaxKind.InterfaceDeclaration, + ) + .map((iface) => + [ + iface, + iface.getExtends().filter((ext) => + ext.getText() === "CCCommandOptions" + ), + ] as const + ) + .filter(([, exts]) => exts.length > 0); + for (const [self, exts] of ifaceExtends) { + for (const ext of exts) { + self.removeExtends(ext); + } + } + + const ccImplementations = file.getDescendantsOfKind( + SyntaxKind.ClassDeclaration, + ).filter((cls) => { + const name = cls.getName(); + if (!name) return false; + if (!name.includes("CC")) return false; + if (name.endsWith("CC")) return false; + return true; + }); + const ctors = ccImplementations.map((cls) => { + const ctors = cls.getConstructors(); + if (ctors.length !== 1) return; + const ctor = ctors[0]; + + // Make sure we have exactly one parameter + const ctorParams = ctor.getParameters(); + if (ctorParams.length !== 1) return; + const ctorParam = ctorParams[0]; + + // with a union type where one is CommandClassDeserializationOptions + const types = ctorParam.getDescendantsOfKind( + SyntaxKind.TypeReference, + ); + let otherType: TypeReferenceNode | undefined; + if ( + types.length === 1 + && types[0].getText() === "CommandClassDeserializationOptions" + ) { + // No other type, need to implement the constructor too + // There is also no if statement + return [cls.getName(), ctor, undefined, undefined] as const; + } else if ( + types.length === 2 + && types.some((type) => + type.getText() === "CommandClassDeserializationOptions" + ) + ) { + // ABCOptions | CommandClassDeserializationOptions + otherType = types.find( + (type) => + type.getText() !== "CommandClassDeserializationOptions", + )!; + } else if ( + types.length === 3 + && types.some((type) => + type.getText() === "CommandClassDeserializationOptions" + ) + && types.some((type) => type.getText() === "CCCommandOptions") + ) { + // (ABCOptions & CCCommandOptions) | CommandClassDeserializationOptions + otherType = types.find( + (type) => + type.getText() !== "CommandClassDeserializationOptions" + && type.getText() !== "CCCommandOptions", + )!; + } else { + return; + } + + // Ensure the constructor contains + // if (gotDeserializationOptions(options)) { + + const ifStatement = ctor.getBody() + ?.getChildrenOfKind(SyntaxKind.IfStatement) + .filter((stmt) => !!stmt.getElseStatement()) + .find((stmt) => { + const expr = stmt.getExpression(); + if (!expr) return false; + return expr.getText() + === "gotDeserializationOptions(options)"; + }); + if (!ifStatement) return; + + return [cls.getName(), ctor, otherType, ifStatement] as const; + }).filter((ctor) => ctor != undefined); + + if (!ctors.length) continue; + + for (const [clsName, ctor, otherType, ifStatement] of ctors) { + // Update the constructor signature + if (otherType) { + ctor.getParameters()[0].setType( + otherType.getText() + " & CCCommandOptions", + ); + } else { + ctor.getParameters()[0].setType( + `${clsName}Options & CCCommandOptions`, + ); + } + + // Replace "this.payload" with just "payload" + const methodBody = ctor.getBody()!.asKind(SyntaxKind.Block)!; + methodBody + ?.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter((expr) => expr.getText() === "this.payload") + .forEach((expr) => { + expr.replaceWithText("payload"); + }); + + // Replace all other assignments with let declarations + const parseImplBlock = ifStatement + ? ifStatement.getThenStatement().asKind( + SyntaxKind.Block, + )! + : methodBody; + const assignments = parseImplBlock + .getDescendantsOfKind(SyntaxKind.BinaryExpression) + .map((be) => { + if ( + be.getOperatorToken().getKind() + !== SyntaxKind.EqualsToken + ) return; + const left = be.getLeft(); + if (!left.isKind(SyntaxKind.PropertyAccessExpression)) { + return; + } + if ( + left.getExpression().getKind() + !== SyntaxKind.ThisKeyword + ) return; + const stmt = be.getParentIfKind( + SyntaxKind.ExpressionStatement, + ); + if (!stmt) return; + const identifier = left.getName(); + if (identifier === "nodeId") return; + const value = be.getRight(); + return [stmt, left, identifier, value] as const; + }) + .filter((ass) => ass != undefined); + + const properties = new Map(); + for (const [expr, left, identifier, value] of assignments) { + if (!properties.has(identifier)) { + // Find the correct type to use + let valueType: string | undefined = value.getType() + .getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + const prop = ctor.getParent().getProperty( + identifier, + ); + if (prop) { + valueType = prop.getType().getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + } + valueType = valueType.replace(/^readonly /, ""); + // Avoid trivially inferred types + const typeIsTrivial = valueType === "number" + || valueType === "number[]"; + + if (expr.getParent() === parseImplBlock) { + // Top level, create a variable declaration + const index = expr.getChildIndex(); + parseImplBlock.insertVariableStatement(index + 1, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + initializer: value.getFullText(), + }], + }); + expr.remove(); + } else { + // Not top level, create an assignment instead + left.replaceWithText(identifier); + // Find the position to create the declaration + let cur: Node = expr; + while (cur.getParent() !== parseImplBlock) { + cur = cur.getParent()!; + } + const index = cur.getChildIndex(); + parseImplBlock.insertVariableStatement(index, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + }], + }); + } + + properties.set(identifier, valueType); + } else { + left.replaceWithText(identifier); + } + } + + // Add a new parse method after the constructor + const ctorIndex = ctor.getChildIndex(); + const method = ctor.getParent().insertMethod(ctorIndex + 1, { + name: "parse", + parameters: [{ + name: "payload", + type: "Buffer", + }, { + name: "options", + type: "CommandClassDeserializationOptions", + }], + isStatic: true, + statements: + // For parse/create constructors, take the then block + ifStatement + ? ifStatement.getThenStatement() + .getChildSyntaxList()! + .getFullText() + // else take the whole constructor without "super()" + : methodBody.getStatementsWithComments().filter((s) => + !s.getText().startsWith("super(") + ).map((s) => s.getText()), + returnType: clsName, + }).toggleModifier("public", true); + + // Instantiate the class at the end of the parse method + method.getFirstDescendantByKind(SyntaxKind.Block)!.addStatements(` +return new ${clsName}({ + nodeId: options.context.sourceNodeId, + ${[...properties.keys()].join(",\n")} +})`); + + if (ifStatement) { + // Replace the `if` block with its else block + ifStatement.replaceWithText( + ifStatement.getElseStatement()!.getChildSyntaxList()! + .getFullText().trimStart(), + ); + } else { + // preserve only the super() call + methodBody.getStatementsWithComments().slice(1).forEach( + (stmt) => { + stmt.remove(); + }, + ); + // And add a best-guess implementation for the constructor + methodBody.addStatements("\n\n// TODO: Check implementation:"); + methodBody.addStatements( + [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return `this.${id} = options.${id};`; + }).join("\n"), + ); + + // Also we probably need to define the options type + const klass = ctor.getParent(); + file.insertInterface(klass.getChildIndex(), { + leadingTrivia: "// @publicAPI\n", + name: `${clsName}Options`, + isExported: true, + properties: [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return { + name: id, + hasQuestionToken: properties.get(id)?.includes( + "undefined", + ), + type: properties.get(id)?.replace( + "| undefined", + "", + ), + }; + }), + }); + } + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorCCParsing.02.ts b/packages/maintenance/src/refactorCCParsing.02.ts new file mode 100644 index 000000000000..84808bb0a152 --- /dev/null +++ b/packages/maintenance/src/refactorCCParsing.02.ts @@ -0,0 +1,126 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/cc/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getBaseNameWithoutExtension().endsWith("CC") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + const ccImplementations = file.getDescendantsOfKind( + SyntaxKind.ClassDeclaration, + ).filter((cls) => { + const name = cls.getName(); + if (!name) return false; + if (!name.includes("CC")) return false; + // if (name.endsWith("CC")) return false; + return true; + }); + + const parse = ccImplementations.map((cls) => { + const method = cls.getMethod("parse"); + if (!method) return; + if (!method.isStatic()) return; + + return method; + }).filter((m) => m != undefined); + + if (!parse.length) continue; + + // Add required imports + const hasRawImport = file.getImportDeclarations().some( + (decl) => + decl.getNamedImports().some((imp) => imp.getName() === "CCRaw"), + ); + if (!hasRawImport) { + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().endsWith("/lib/CommandClass") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "../lib/CommandClass", + namedImports: [{ + name: "CCRaw", + isTypeOnly: true, + }], + }); + } else { + existing.addNamedImport({ + name: "CCRaw", + isTypeOnly: true, + }); + } + } + + const hasCCParsingContextImport = file.getImportDeclarations().some( + (decl) => + decl.getNamedImports().some((imp) => + imp.getName() === "CCParsingContext" + ), + ); + if (!hasCCParsingContextImport) { + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith("@zwave-js/host") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "@zwave-js/host", + namedImports: [{ + name: "CCParsingContext", + isTypeOnly: true, + }], + }); + } else { + existing.addNamedImport({ + name: "CCParsingContext", + isTypeOnly: true, + }); + } + } + + for (const impl of parse) { + // Update the method signature + impl.rename("from"); + impl.getParameters().forEach((p) => p.remove()); + impl.addParameter({ + name: "raw", + type: "CCRaw", + }); + impl.addParameter({ + name: "ctx", + type: "CCParsingContext", + }); + + // Replace "payload" with "raw.payload" + const idents = impl.getDescendantsOfKind(SyntaxKind.Identifier) + .filter((id) => id.getText() === "payload"); + idents.forEach((id) => id.replaceWithText("raw.payload")); + + // Replace "options.context.sourceNodeId" with "ctx.sourceNodeId" + const exprs = impl.getDescendantsOfKind( + SyntaxKind.PropertyAccessExpression, + ).filter((expr) => + expr.getText() === "options.context.sourceNodeId" + ); + exprs.forEach((expr) => expr.replaceWithText("ctx.sourceNodeId")); + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorCCParsing.03.ts b/packages/maintenance/src/refactorCCParsing.03.ts new file mode 100644 index 000000000000..9d39a56b15eb --- /dev/null +++ b/packages/maintenance/src/refactorCCParsing.03.ts @@ -0,0 +1,79 @@ +import fs from "node:fs/promises"; +import { type IntersectionTypeNode, Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/cc/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getBaseNameWithoutExtension().endsWith("CC") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + // Add required imports + const hasWithAddressImport = file.getImportDeclarations().some( + (decl) => + decl.getNamedImports().some((imp) => + imp.getName() === "WithAddress" + ), + ); + if (!hasWithAddressImport) { + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith("@zwave-js/core") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "@zwave-js/core", + namedImports: [{ + name: "WithAddress", + isTypeOnly: true, + }], + }); + } else { + existing.addNamedImport({ + name: "WithAddress", + isTypeOnly: true, + }); + } + } + + // Remove old imports + const oldImports = file.getImportDeclarations().map((decl) => + decl.getNamedImports().find( + (imp) => imp.getName() === "CCCommandOptions", + ) + ).filter((i) => i != undefined); + for (const imp of oldImports) { + imp.remove(); + } + + const oldTypes = file.getDescendantsOfKind(SyntaxKind.TypeReference) + .filter((ref) => ref.getText() === "CCCommandOptions") + .map((ref) => ref.getParentIfKind(SyntaxKind.IntersectionType)) + .filter((typ): typ is IntersectionTypeNode => + typ != undefined + && !!typ.getParent().isKind(SyntaxKind.Parameter) + && !!typ.getParent().getParent()?.isKind(SyntaxKind.Constructor) + ); + for (const type of oldTypes) { + const otherType = type.getText().replace("& CCCommandOptions", "") + .replace("CCCommandOptions & ", ""); + type.replaceWithText(`WithAddress<${otherType}>`); + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorCCParsing.04.ts b/packages/maintenance/src/refactorCCParsing.04.ts new file mode 100644 index 000000000000..58d91526bf2a --- /dev/null +++ b/packages/maintenance/src/refactorCCParsing.04.ts @@ -0,0 +1,69 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/cc/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getBaseNameWithoutExtension().endsWith("CC") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + const emptyFromImpls = file.getDescendantsOfKind( + SyntaxKind.MethodDeclaration, + ) + .filter((m) => m.isStatic() && m.getName() === "from") + .filter((m) => { + const params = m.getParameters(); + if (params.length !== 2) return false; + if ( + params[0].getDescendantsOfKind(SyntaxKind.TypeReference)[0] + ?.getText() !== "CCRaw" + ) return false; + if ( + params[1].getDescendantsOfKind(SyntaxKind.TypeReference)[0] + ?.getText() !== "CCParsingContext" + ) { + return false; + } + return true; + }) + .filter((m) => { + const body = m.getBody()?.asKind(SyntaxKind.Block); + if (!body) return false; + const firstStmt = body.getStatements()[0]; + if (!firstStmt) return false; + if ( + firstStmt.isKind(SyntaxKind.ThrowStatement) + && firstStmt.getText().includes("ZWaveError") + ) { + return true; + } + return false; + }); + + if (emptyFromImpls.length === 0) continue; + + for (const impl of emptyFromImpls) { + for (const param of impl.getParameters()) { + param.rename("_" + param.getName()); + } + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorMessageParsing.01.ts b/packages/maintenance/src/refactorMessageParsing.01.ts new file mode 100644 index 000000000000..db6d34e32f68 --- /dev/null +++ b/packages/maintenance/src/refactorMessageParsing.01.ts @@ -0,0 +1,430 @@ +import fs from "node:fs/promises"; +import { + type Node, + Project, + SyntaxKind, + VariableDeclarationKind, +} from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/zwave-js/tsconfig.json", + }); + // project.addSourceFilesAtPaths("packages/cc/src/cc/**/*CC.ts"); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getFilePath().includes("lib/serialapi/") + ); + for (const file of sourceFiles) { + // const filePath = path.relative(process.cwd(), file.getFilePath()); + + // Remove `extends MessageBaseOptions` + const ifaceExtends = file.getDescendantsOfKind( + SyntaxKind.InterfaceDeclaration, + ) + .map((iface) => + [ + iface, + iface.getExtends().filter((ext) => + ext.getText() === "MessageBaseOptions" + ), + ] as const + ) + .filter(([, exts]) => exts.length > 0); + for (const [self, exts] of ifaceExtends) { + for (const ext of exts) { + self.removeExtends(ext); + self.toggleModifier("export", true); + } + } + + const msgImplementations = file.getDescendantsOfKind( + SyntaxKind.ClassDeclaration, + ).filter((cls) => { + if (cls.getExtends()?.getText() === "Message") return true; + + const name = cls.getName(); + if (!name) return false; + // if (name.endsWith("Base")) return false; + return name.includes("Request") + || name.endsWith("Response") + || name.endsWith("TransmitReport") + || name.endsWith("StatusReport") + || name.endsWith("Callback"); + }); + const ctors = msgImplementations.map((cls) => { + const ctors = cls.getConstructors(); + if (ctors.length !== 1) return; + const ctor = ctors[0]; + + // Make sure we have exactly one parameter + const ctorParams = ctor.getParameters(); + if (ctorParams.length !== 1) return; + const ctorParam = ctorParams[0]; + + if (cls.getName()!.startsWith("SendDataMulticastRequest")) debugger; + + // with a union type where one is MessageDeserializationOptions + const types = ctorParam.getDescendantsOfKind( + SyntaxKind.TypeReference, + ).map((t) => t.getText()); + let otherType: string | undefined; + if ( + types.length === 1 + && types[0] === "MessageDeserializationOptions" + ) { + // No other type, need to implement the constructor too + // There is also no if statement + return [cls.getName(), ctor, undefined, undefined] as const; + } else if ( + types.length === 1 + && types[0] === "MessageOptions" + ) { + // Actually MessageBaseOptions | MessageDeserializationOptions + otherType = "MessageBaseOptions"; + } else if ( + types.length === 2 + && types.includes("MessageDeserializationOptions") + ) { + // ABCOptions | MessageDeserializationOptions + otherType = types.find( + (type) => type !== "MessageDeserializationOptions", + ); + } else if ( + types.length === 3 + && types.includes("MessageDeserializationOptions") + && types.some((generic) => + types.some((type) => type.includes(`<${generic}>`)) + ) + ) { + // ABCOptions | MessageDeserializationOptions + otherType = types.find( + (type) => + types.some((other) => type.includes(`<${other}>`)), + ); + } else if ( + types.length === 3 + && types.includes("MessageDeserializationOptions") + && types.includes("MessageBaseOptions") + ) { + // (ABCOptions & MessageBaseOptions) | MessageDeserializationOptions + otherType = types.find( + (type) => + type !== "MessageDeserializationOptions" + && type !== "MessageBaseOptions", + ); + } else { + return; + } + + // Ensure the constructor contains + // if (gotDeserializationOptions(options)) { + + const ifStatement = ctor.getBody() + ?.getChildrenOfKind(SyntaxKind.IfStatement) + .filter((stmt) => !!stmt.getElseStatement()) + .find((stmt) => { + const expr = stmt.getExpression(); + if (!expr) return false; + return expr.getText() + === "gotDeserializationOptions(options)"; + }); + if (!ifStatement) return; + + return [cls.getName(), ctor, otherType, ifStatement] as const; + }).filter((ctor) => ctor != undefined); + + if (!ctors.length) continue; + + // Add required imports + const hasRawImport = file.getImportDeclarations().some( + (decl) => + decl.getNamedImports().some((imp) => + imp.getName() === "MessageRaw" + ), + ); + if (!hasRawImport) { + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith("@zwave-js/serial") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "@zwave-js/serial", + namedImports: [{ + name: "MessageRaw", + isTypeOnly: true, + }], + }); + } else { + existing.addNamedImport({ + name: "MessageRaw", + isTypeOnly: true, + }); + } + } + + const hasContextImport = file.getImportDeclarations().some( + (decl) => + decl.getNamedImports().some((imp) => + imp.getName() === "MessageParsingContext" + ), + ); + if (!hasContextImport) { + const existing = file.getImportDeclaration((decl) => + decl.getModuleSpecifierValue().startsWith("@zwave-js/serial") + ); + if (!existing) { + file.addImportDeclaration({ + moduleSpecifier: "@zwave-js/serial", + namedImports: [{ + name: "MessageParsingContext", + isTypeOnly: true, + }], + }); + } else { + existing.addNamedImport({ + name: "MessageParsingContext", + isTypeOnly: true, + }); + } + } + + // Remove old imports + const oldImports = file.getImportDeclarations().flatMap((decl) => + decl.getNamedImports().filter( + (imp) => + imp.getName() === "MessageDeserializationOptions" + || imp.getName() === "gotDeserializationOptions", + ) + ); + for (const imp of oldImports) { + imp.remove(); + } + + for (const [clsName, ctor, otherType, ifStatement] of ctors) { + // Update the constructor signature + if (otherType === "MessageBaseOptions") { + ctor.getParameters()[0].setType( + "MessageBaseOptions", + ); + } else if (otherType) { + ctor.getParameters()[0].setType( + otherType + " & MessageBaseOptions", + ); + } else { + ctor.getParameters()[0].setType( + `${clsName}Options & MessageBaseOptions`, + ); + } + + // Replace "this.payload" with "raw.payload" + const methodBody = ctor.getBody()!.asKind(SyntaxKind.Block)!; + methodBody + ?.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter((expr) => expr.getText() === "this.payload") + .forEach((expr) => { + expr.replaceWithText("raw.payload"); + }); + + // Replace all other assignments with let declarations + const parseImplBlock = ifStatement + ? ifStatement.getThenStatement().asKind( + SyntaxKind.Block, + )! + : methodBody; + const assignments = parseImplBlock + .getDescendantsOfKind(SyntaxKind.BinaryExpression) + .map((be) => { + if ( + be.getOperatorToken().getKind() + !== SyntaxKind.EqualsToken + ) return; + const left = be.getLeft(); + if (!left.isKind(SyntaxKind.PropertyAccessExpression)) { + return; + } + if ( + left.getExpression().getKind() + !== SyntaxKind.ThisKeyword + ) return; + const stmt = be.getParentIfKind( + SyntaxKind.ExpressionStatement, + ); + if (!stmt) return; + const identifier = left.getName(); + if ( + identifier === "ownNodeId" + || identifier === "_ownNodeId" + ) return; + const value = be.getRight(); + return [stmt, left, identifier, value] as const; + }) + .filter((ass) => ass != undefined); + + const properties = new Map(); + for (const [expr, left, identifier, value] of assignments) { + if (!properties.has(identifier)) { + // Find the correct type to use + let valueType: string | undefined = value.getType() + .getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + const prop = ctor.getParent().getProperty( + identifier, + ); + if (prop) { + valueType = prop.getType().getText().replaceAll( + /import\(.*?\)\./g, + "", + ); + } + valueType = valueType.replace(/^readonly /, ""); + // Avoid trivially inferred types + const typeIsTrivial = valueType === "number" + || valueType === "number[]"; + + if (expr.getParent() === parseImplBlock) { + // Top level, create a variable declaration + const index = expr.getChildIndex(); + parseImplBlock.insertVariableStatement(index + 1, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + initializer: value.getFullText(), + }], + }); + expr.remove(); + } else { + // Not top level, create an assignment instead + left.replaceWithText(identifier); + // Find the position to create the declaration + let cur: Node = expr; + while (cur.getParent() !== parseImplBlock) { + cur = cur.getParent()!; + } + const index = cur.getChildIndex(); + parseImplBlock.insertVariableStatement(index, { + declarationKind: VariableDeclarationKind.Let, + declarations: [{ + name: identifier, + type: typeIsTrivial ? undefined : valueType, + }], + }); + } + + properties.set(identifier, valueType); + } else { + left.replaceWithText(identifier); + } + } + + // Replace all occurences of this.xxx with just xxx + const thisAccesses = parseImplBlock.getDescendantsOfKind( + SyntaxKind.PropertyAccessExpression, + ) + .filter((expr) => + !!expr.getExpressionIfKind(SyntaxKind.ThisKeyword) + ); + for (const expr of thisAccesses) { + expr.replaceWithText(expr.getName()); + } + + // Replace options.ctx with ctx + const optionsDotCtx = parseImplBlock.getDescendantsOfKind( + SyntaxKind.PropertyAccessExpression, + ) + .filter((expr) => expr.getText() === "options.ctx"); + for (const expr of optionsDotCtx) { + expr.replaceWithText("ctx"); + } + + // Add a new parse method after the constructor + const ctorIndex = ctor.getChildIndex(); + const method = ctor.getParent().insertMethod(ctorIndex + 1, { + name: "from", + parameters: [{ + name: "raw", + type: "MessageRaw", + }, { + name: "ctx", + type: "MessageParsingContext", + }], + isStatic: true, + statements: + // For parse/create constructors, take the then block + ifStatement + ? ifStatement.getThenStatement() + .getChildSyntaxList()! + .getFullText() + // else take the whole constructor without "super()" + : methodBody.getStatementsWithComments().filter((s) => + !s.getText().startsWith("super(") + ).map((s) => s.getText()), + returnType: clsName, + }).toggleModifier("public", true); + + // Instantiate the class at the end of the parse method + method.getFirstDescendantByKind(SyntaxKind.Block)!.addStatements(` +return new ${clsName}({ + ${[...properties.keys()].join(",\n")} +})`); + + if (ifStatement) { + // Replace the `if` block with its else block + ifStatement.replaceWithText( + ifStatement.getElseStatement()!.getChildSyntaxList()! + .getFullText().trimStart(), + ); + } else { + // preserve only the super() call + methodBody.getStatementsWithComments().slice(1).forEach( + (stmt) => { + stmt.remove(); + }, + ); + // And add a best-guess implementation for the constructor + methodBody.addStatements("\n\n// TODO: Check implementation:"); + methodBody.addStatements( + [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return `this.${id} = options.${id};`; + }).join("\n"), + ); + + // Also we probably need to define the options type + const klass = ctor.getParent(); + file.insertInterface(klass.getChildIndex(), { + name: `${clsName}Options`, + isExported: true, + properties: [...properties.keys()].map((id) => { + if (id.startsWith("_")) id = id.slice(1); + return { + name: id, + hasQuestionToken: properties.get(id)?.includes( + "undefined", + ), + type: properties.get(id)?.replace( + "| undefined", + "", + ), + }; + }), + }); + } + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorMessageParsing.02.ts b/packages/maintenance/src/refactorMessageParsing.02.ts new file mode 100644 index 000000000000..1c7f9f159266 --- /dev/null +++ b/packages/maintenance/src/refactorMessageParsing.02.ts @@ -0,0 +1,68 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/zwave-js/tsconfig.json", + }); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getFilePath().includes("lib/serialapi/") + ); + for (const file of sourceFiles) { + const emptyFromImpls = file.getDescendantsOfKind( + SyntaxKind.MethodDeclaration, + ) + .filter((m) => m.isStatic() && m.getName() === "from") + .filter((m) => { + const params = m.getParameters(); + if (params.length !== 2) return false; + if ( + params[0].getDescendantsOfKind(SyntaxKind.TypeReference)[0] + ?.getText() !== "MessageRaw" + ) return false; + if ( + params[1].getDescendantsOfKind(SyntaxKind.TypeReference)[0] + ?.getText() !== "MessageParsingContext" + ) { + return false; + } + return true; + }) + .filter((m) => { + const body = m.getBody()?.asKind(SyntaxKind.Block); + if (!body) return false; + const firstStmt = body.getStatements()[0]; + if (!firstStmt) return false; + if ( + firstStmt.isKind(SyntaxKind.ThrowStatement) + && firstStmt.getText().includes("ZWaveError") + ) { + return true; + } + return false; + }); + + if (emptyFromImpls.length === 0) continue; + + for (const impl of emptyFromImpls) { + for (const param of impl.getParameters()) { + if (!param.getName().startsWith("_")) { + param.rename("_" + param.getName()); + } + } + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/maintenance/src/refactorMessageParsing.03.ts b/packages/maintenance/src/refactorMessageParsing.03.ts new file mode 100644 index 000000000000..c57b0b2041c2 --- /dev/null +++ b/packages/maintenance/src/refactorMessageParsing.03.ts @@ -0,0 +1,61 @@ +import fs from "node:fs/promises"; +import { Project, SyntaxKind } from "ts-morph"; + +async function main() { + const project = new Project({ + tsConfigFilePath: "packages/zwave-js/tsconfig.json", + }); + + const sourceFiles = project.getSourceFiles().filter((file) => + file.getFilePath().includes("lib/serialapi/") + ); + for (const file of sourceFiles) { + const fromImplsReturningSelf = file + .getDescendantsOfKind(SyntaxKind.MethodDeclaration) + .filter((m) => m.isStatic() && m.getName() === "from") + .map((m) => { + const clsName = m + .getParentIfKind(SyntaxKind.ClassDeclaration) + ?.getName(); + if (clsName === "AssignSUCReturnRouteRequest") debugger; + if (m.getReturnTypeNode()?.getText() === clsName) { + return [clsName, m] as const; + } + }) + .filter((m) => m != undefined); + + const returnSelfStmts = fromImplsReturningSelf.flatMap( + ([clsName, method]) => { + if (clsName === "AssignSUCReturnRouteRequest") debugger; + + return method + .getDescendantsOfKind(SyntaxKind.ReturnStatement) + .map((ret) => + ret.getExpressionIfKind(SyntaxKind.NewExpression) + ) + .filter((newexp) => + newexp?.getExpressionIfKind(SyntaxKind.Identifier) + ?.getText() === clsName + ) + .filter((n) => n != undefined); + }, + ); + if (returnSelfStmts.length === 0) continue; + + for (const ret of returnSelfStmts) { + ret.setExpression("this"); + } + + await file.save(); + } +} + +void main().catch(async (e) => { + await fs.writeFile(`${e.filePath}.old`, e.oldText); + await fs.writeFile(`${e.filePath}.new`, e.newText); + console.error(`Error refactoring file ${e.filePath} + old text: ${e.filePath}.old + new text: ${e.filePath}.new`); + + process.exit(1); +}); diff --git a/packages/serial/package.json b/packages/serial/package.json index 3d99e35b6449..e41ffd506f08 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -17,6 +17,11 @@ "types": "./build/index_safe.d.ts", "default": "./build/index_safe.js" }, + "./serialapi": { + "@@dev": "./src/index_serialapi.ts", + "types": "./build/index_serialapi.d.ts", + "default": "./build/index_serialapi.js" + }, "./mock": { "@@dev": "./src/index_mock.ts", "types": "./build/index_mock.d.ts", @@ -58,6 +63,7 @@ }, "dependencies": { "@serialport/stream": "^12.0.0", + "@zwave-js/cc": "workspace:*", "@zwave-js/core": "workspace:*", "@zwave-js/host": "workspace:*", "@zwave-js/shared": "workspace:*", diff --git a/packages/serial/serial.api.md b/packages/serial/serial.api.md index fb5479473df6..f3d7bb97412d 100644 --- a/packages/serial/serial.api.md +++ b/packages/serial/serial.api.md @@ -8,22 +8,28 @@ import type { DataDirection } from '@zwave-js/core/safe'; import { DataDirection as DataDirection_2 } from '@zwave-js/core'; import { Duplex } from 'node:stream'; import { EventEmitter } from 'node:events'; -import { IZWaveNode } from '@zwave-js/core'; +import type { GetDeviceConfig } from '@zwave-js/host'; +import type { GetNode } from '@zwave-js/host'; +import type { GetSupportedCCVersion } from '@zwave-js/host'; +import type { HostIDs } from '@zwave-js/host'; import type { JSONObject } from '@zwave-js/shared/safe'; import type { LogContext } from '@zwave-js/core/safe'; +import { MaybeNotKnown } from '@zwave-js/core'; import { MessageOrCCLogEntry } from '@zwave-js/core'; import { MessagePriority } from '@zwave-js/core'; import type * as net from 'node:net'; +import { NodeId } from '@zwave-js/core'; +import { NodeIDType } from '@zwave-js/core'; import { PassThrough } from 'node:stream'; import { RSSI } from '@zwave-js/core'; +import { SecurityClass } from '@zwave-js/core'; +import { SecurityManagers } from '@zwave-js/core'; import { SerialPort } from 'serialport'; import { Transform } from 'node:stream'; import { TransformCallback } from 'node:stream'; import type { TypedClassDecorator } from '@zwave-js/shared/safe'; import { UnknownZWaveChipType } from '@zwave-js/core'; import { ZnifferProtocolDataRate } from '@zwave-js/core'; -import type { ZWaveApplicationHost } from '@zwave-js/host'; -import type { ZWaveHost } from '@zwave-js/host'; import { ZWaveLogContainer } from '@zwave-js/core'; import { ZWaveLoggerBase } from '@zwave-js/core'; @@ -88,11 +94,6 @@ export class BootloaderScreenParser extends Transform { _transform(chunk: any, encoding: string, callback: TransformCallback): void; } -// Warning: (ae-missing-release-tag) "DeserializingMessageConstructor" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type DeserializingMessageConstructor = new (host: ZWaveHost, options: MessageDeserializationOptions) => T; - // Warning: (ae-missing-release-tag) "DeserializingZnifferMessageConstructor" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -458,11 +459,6 @@ export function getMessageType(messageClass: T): MessageType // @public export function getMessageTypeStatic>(classConstructor: T): MessageType | undefined; -// Warning: (ae-missing-release-tag) "gotDeserializationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function gotDeserializationOptions(options: Record | undefined): options is MessageDeserializationOptions; - // Warning: (ae-missing-release-tag) "INodeQuery" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -495,9 +491,12 @@ export function isZWaveSerialPortImplementation(obj: unknown): obj is ZWaveSeria // // @public export class Message { - constructor(host: ZWaveHost, options?: MessageOptions); - get callbackId(): number; - set callbackId(v: number | undefined); + constructor(options?: MessageOptions); + // (undocumented) + protected assertCallbackId(): asserts this is this & { + callbackId: number; + }; + callbackId: number | undefined; // (undocumented) get completedTimestamp(): number | undefined; // (undocumented) @@ -508,20 +507,15 @@ export class Message { expectsCallback(): boolean; expectsNodeUpdate(): boolean; expectsResponse(): boolean; - static extractPayload(data: Buffer): Buffer; - static from(host: ZWaveHost, options: MessageDeserializationOptions, contextStore?: Map>): Message; + static from(raw: MessageRaw, ctx: MessageParsingContext): Message; // (undocumented) functionType: FunctionType; getCallbackTimeout(): number | undefined; - static getConstructor(data: Buffer): MessageConstructor; - static getMessageLength(data: Buffer): number; getNodeId(): number | undefined; - getNodeUnsafe(applHost: ZWaveApplicationHost): IZWaveNode | undefined; getResponseTimeout(): number | undefined; - hasCallbackId(): boolean; - // (undocumented) - readonly host: ZWaveHost; - static isComplete(data?: Buffer): boolean; + hasCallbackId(): this is this & { + callbackId: number; + }; isExpectedCallback(msg: Message): boolean; isExpectedNodeUpdate(msg: Message): boolean; isExpectedResponse(msg: Message): boolean; @@ -530,13 +524,16 @@ export class Message { needsCallbackId(): boolean; nodeUpdateTimeout: number | undefined; // (undocumented) + static parse(data: Buffer, ctx: MessageParsingContext): Message; + // (undocumented) payload: Buffer; prematureNodeUpdate: Message | undefined; get rtt(): number | undefined; - serialize(): Buffer; + serialize(ctx: MessageEncodingContext): Buffer; toJSON(): JSONObject; toLogEntry(): MessageOrCCLogEntry; get transmissionTimestamp(): number | undefined; + tryGetNode(ctx: GetNode): T | undefined; // (undocumented) type: MessageType; } @@ -552,35 +549,21 @@ export interface MessageBaseOptions { // Warning: (ae-missing-release-tag) "MessageConstructor" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type MessageConstructor = new (host: ZWaveHost, options?: MessageOptions) => T; +export type MessageConstructor = typeof Message & { + new (options: MessageBaseOptions): T; +}; -// Warning: (ae-missing-release-tag) "MessageCreationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "MessageEncodingContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface MessageCreationOptions extends MessageBaseOptions { +export interface MessageEncodingContext extends Readonly, HostIDs, GetSupportedCCVersion, GetDeviceConfig { // (undocumented) - expectedCallback?: FunctionType | typeof Message | ResponsePredicate; + getHighestSecurityClass(nodeId: number): MaybeNotKnown; // (undocumented) - expectedResponse?: FunctionType | typeof Message | ResponsePredicate; + hasSecurityClass(nodeId: number, securityClass: SecurityClass): MaybeNotKnown; + nodeIdType: NodeIDType; // (undocumented) - functionType?: FunctionType; - // (undocumented) - payload?: Buffer; - // (undocumented) - type?: MessageType; -} - -// Warning: (ae-missing-release-tag) "MessageDeserializationOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface MessageDeserializationOptions { - context?: unknown; - // (undocumented) - data: Buffer; - // (undocumented) - origin?: MessageOrigin; - parseCCs?: boolean; - sdkVersion?: string; + setSecurityClass(nodeId: number, securityClass: SecurityClass, granted: boolean): void; } // Warning: (ae-missing-release-tag) "MessageHeaders" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -600,7 +583,18 @@ export enum MessageHeaders { // Warning: (ae-missing-release-tag) "MessageOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type MessageOptions = MessageCreationOptions | MessageDeserializationOptions; +export interface MessageOptions extends MessageBaseOptions { + // (undocumented) + expectedCallback?: FunctionType | typeof Message | ResponsePredicate; + // (undocumented) + expectedResponse?: FunctionType | typeof Message | ResponsePredicate; + // (undocumented) + functionType?: FunctionType; + // (undocumented) + payload?: Buffer; + // (undocumented) + type?: MessageType; +} // Warning: (ae-missing-release-tag) "MessageOrigin" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -612,6 +606,36 @@ export enum MessageOrigin { Host = 1 } +// Warning: (ae-missing-release-tag) "MessageParsingContext" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MessageParsingContext extends HostIDs, GetDeviceConfig { + nodeIdType: NodeIDType; + // (undocumented) + origin?: MessageOrigin; + // (undocumented) + requestStorage: Map> | undefined; + // (undocumented) + sdkVersion: string | undefined; +} + +// Warning: (ae-missing-release-tag) "MessageRaw" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class MessageRaw { + constructor(type: MessageType, functionType: FunctionType, payload: Buffer); + // (undocumented) + readonly functionType: FunctionType; + // (undocumented) + static parse(data: Buffer): MessageRaw; + // (undocumented) + readonly payload: Buffer; + // (undocumented) + readonly type: MessageType; + // (undocumented) + withPayload(payload: Buffer): MessageRaw; +} + // Warning: (ae-missing-release-tag) "MessageType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/packages/serial/src/index.ts b/packages/serial/src/index.ts index 5c20a1baac3e..30ea53e36711 100644 --- a/packages/serial/src/index.ts +++ b/packages/serial/src/index.ts @@ -1,19 +1,20 @@ /* eslint-disable @typescript-eslint/consistent-type-exports */ -export { SerialLogger } from "./Logger"; -export type { SerialLogContext } from "./Logger_safe"; -export * from "./MessageHeaders"; -export * from "./ZWaveSerialPort"; -export * from "./ZWaveSerialPortBase"; -export * from "./ZWaveSerialPortImplementation"; -export * from "./ZWaveSocket"; -export * from "./ZWaveSocketOptions"; -export * from "./ZnifferSerialPort"; -export * from "./ZnifferSerialPortBase"; -export * from "./ZnifferSocket"; +export { SerialLogger } from "./log/Logger"; +export type { SerialLogContext } from "./log/Logger_safe"; export * from "./message/Constants"; -export * from "./message/INodeQuery"; export * from "./message/Message"; +export * from "./message/MessageHeaders"; export * from "./message/SuccessIndicator"; export * from "./message/ZnifferMessages"; export * from "./parsers/BootloaderParsers"; export * from "./parsers/SerialAPIParser"; +export * from "./serialport/ZWaveSerialPort"; +export * from "./serialport/ZWaveSerialPortBase"; +export * from "./serialport/ZWaveSerialPortImplementation"; +export * from "./serialport/ZWaveSocket"; +export * from "./serialport/ZWaveSocketOptions"; +export * from "./zniffer/ZnifferSerialPort"; +export * from "./zniffer/ZnifferSerialPortBase"; +export * from "./zniffer/ZnifferSocket"; + +export * from "./index_serialapi"; diff --git a/packages/serial/src/index_mock.ts b/packages/serial/src/index_mock.ts index 5d748f40ef90..f8a96856b1d9 100644 --- a/packages/serial/src/index_mock.ts +++ b/packages/serial/src/index_mock.ts @@ -1,3 +1,3 @@ -export * from "./MockSerialPort"; -export * from "./SerialPortBindingMock"; -export * from "./SerialPortMock"; +export * from "./mock/MockSerialPort"; +export * from "./mock/SerialPortBindingMock"; +export * from "./mock/SerialPortMock"; diff --git a/packages/serial/src/index_safe.ts b/packages/serial/src/index_safe.ts index 016332917420..861445cd1ced 100644 --- a/packages/serial/src/index_safe.ts +++ b/packages/serial/src/index_safe.ts @@ -1,6 +1,6 @@ /* @forbiddenImports external */ -export type { SerialLogContext } from "./Logger_safe"; -export * from "./MessageHeaders"; +export type { SerialLogContext } from "./log/Logger_safe"; export * from "./message/Constants"; +export * from "./message/MessageHeaders"; export * from "./message/SuccessIndicator"; diff --git a/packages/serial/src/index_serialapi.ts b/packages/serial/src/index_serialapi.ts new file mode 100644 index 000000000000..cde76496ee47 --- /dev/null +++ b/packages/serial/src/index_serialapi.ts @@ -0,0 +1,56 @@ +export * from "./serialapi/application/ApplicationCommandRequest"; +export * from "./serialapi/application/ApplicationUpdateRequest"; +export * from "./serialapi/application/BridgeApplicationCommandRequest"; +export * from "./serialapi/application/SerialAPIStartedRequest"; +export * from "./serialapi/application/ShutdownMessages"; +export * from "./serialapi/capability/GetControllerCapabilitiesMessages"; +export * from "./serialapi/capability/GetControllerVersionMessages"; +export * from "./serialapi/capability/GetLongRangeNodesMessages"; +export * from "./serialapi/capability/GetProtocolVersionMessages"; +export * from "./serialapi/capability/GetSerialApiCapabilitiesMessages"; +export * from "./serialapi/capability/GetSerialApiInitDataMessages"; +export * from "./serialapi/capability/HardResetRequest"; +export * from "./serialapi/capability/LongRangeChannelMessages"; +export * from "./serialapi/capability/SerialAPISetupMessages"; +export * from "./serialapi/capability/SetApplicationNodeInformationRequest"; +export * from "./serialapi/capability/SetLongRangeShadowNodeIDsRequest"; +export * from "./serialapi/memory/GetControllerIdMessages"; +export * from "./serialapi/misc/GetBackgroundRSSIMessages"; +export * from "./serialapi/misc/SetRFReceiveModeMessages"; +export * from "./serialapi/misc/SetSerialApiTimeoutsMessages"; +export * from "./serialapi/misc/SoftResetRequest"; +export * from "./serialapi/misc/WatchdogMessages"; +export * from "./serialapi/network-mgmt/AddNodeToNetworkRequest"; +export * from "./serialapi/network-mgmt/AssignPriorityReturnRouteMessages"; +export * from "./serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages"; +export * from "./serialapi/network-mgmt/AssignReturnRouteMessages"; +export * from "./serialapi/network-mgmt/AssignSUCReturnRouteMessages"; +export * from "./serialapi/network-mgmt/DeleteReturnRouteMessages"; +export * from "./serialapi/network-mgmt/DeleteSUCReturnRouteMessages"; +export * from "./serialapi/network-mgmt/GetNodeProtocolInfoMessages"; +export * from "./serialapi/network-mgmt/GetPriorityRouteMessages"; +export * from "./serialapi/network-mgmt/GetRoutingInfoMessages"; +export * from "./serialapi/network-mgmt/GetSUCNodeIdMessages"; +export * from "./serialapi/network-mgmt/IsFailedNodeMessages"; +export * from "./serialapi/network-mgmt/RemoveFailedNodeMessages"; +export * from "./serialapi/network-mgmt/RemoveNodeFromNetworkRequest"; +export * from "./serialapi/network-mgmt/ReplaceFailedNodeRequest"; +export * from "./serialapi/network-mgmt/RequestNodeInfoMessages"; +export * from "./serialapi/network-mgmt/RequestNodeNeighborUpdateMessages"; +export * from "./serialapi/network-mgmt/SetLearnModeMessages"; +export * from "./serialapi/network-mgmt/SetPriorityRouteMessages"; +export * from "./serialapi/network-mgmt/SetSUCNodeIDMessages"; +export * from "./serialapi/nvm/ExtNVMReadLongBufferMessages"; +export * from "./serialapi/nvm/ExtNVMReadLongByteMessages"; +export * from "./serialapi/nvm/ExtNVMWriteLongBufferMessages"; +export * from "./serialapi/nvm/ExtNVMWriteLongByteMessages"; +export * from "./serialapi/nvm/ExtendedNVMOperationsMessages"; +export * from "./serialapi/nvm/FirmwareUpdateNVMMessages"; +export * from "./serialapi/nvm/GetNVMIdMessages"; +export * from "./serialapi/nvm/NVMOperationsMessages"; +export * from "./serialapi/transport/SendDataBridgeMessages"; +export * from "./serialapi/transport/SendDataMessages"; +export * from "./serialapi/transport/SendDataShared"; +export * from "./serialapi/transport/SendTestFrameMessages"; + +export * from "./serialapi/utils"; diff --git a/packages/serial/src/Logger.ts b/packages/serial/src/log/Logger.ts similarity index 98% rename from packages/serial/src/Logger.ts rename to packages/serial/src/log/Logger.ts index c4a0feb16de2..ccc4b6918646 100644 --- a/packages/serial/src/Logger.ts +++ b/packages/serial/src/log/Logger.ts @@ -5,12 +5,12 @@ import { getDirectionPrefix, } from "@zwave-js/core"; import { buffer2hex, getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { MessageHeaders } from "../message/MessageHeaders"; import { SERIAL_LABEL, SERIAL_LOGLEVEL, type SerialLogContext, } from "./Logger_safe"; -import { MessageHeaders } from "./MessageHeaders"; export class SerialLogger extends ZWaveLoggerBase { constructor(loggers: ZWaveLogContainer) { diff --git a/packages/serial/src/Logger_safe.ts b/packages/serial/src/log/Logger_safe.ts similarity index 100% rename from packages/serial/src/Logger_safe.ts rename to packages/serial/src/log/Logger_safe.ts diff --git a/packages/serial/src/message/Constants.ts b/packages/serial/src/message/Constants.ts index 9b3fcf531cfb..a156a492d5bc 100644 --- a/packages/serial/src/message/Constants.ts +++ b/packages/serial/src/message/Constants.ts @@ -1,4 +1,4 @@ -import { ZnifferMessageHeaders } from "../MessageHeaders"; +import { ZnifferMessageHeaders } from "./MessageHeaders"; /** Indicates the type of a data message */ export enum MessageType { diff --git a/packages/serial/src/message/INodeQuery.ts b/packages/serial/src/message/INodeQuery.ts deleted file mode 100644 index 9b93e7c572c3..000000000000 --- a/packages/serial/src/message/INodeQuery.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Message } from "./Message"; - -export interface INodeQuery { - nodeId: number; -} - -/** Tests if the given message is for a node or references a node */ -export function isNodeQuery(msg: T): msg is T & INodeQuery { - return typeof (msg as any).nodeId === "number"; -} diff --git a/packages/serial/src/message/Message.test.ts b/packages/serial/src/message/Message.test.ts index 72be3ca3b0af..7d565a56b882 100644 --- a/packages/serial/src/message/Message.test.ts +++ b/packages/serial/src/message/Message.test.ts @@ -2,11 +2,9 @@ import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; import { createTestingHost } from "@zwave-js/host"; import test from "ava"; import { FunctionType, MessageType } from "./Constants"; -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 +38,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 = Message.parse(original, {} 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,164 +88,19 @@ 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, - }); - } -}); - -test("isComplete() should work correctly", (t) => { - // actual messages from OZW - const okayMessages = [ - Buffer.from([ - 0x01, - 0x09, - 0x00, - 0x13, - 0x03, - 0x02, - 0x00, - 0x00, - 0x25, - 0x0b, - 0xca, - ]), - Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), - Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), - Buffer.from([ - 0x01, - 0x0a, - 0x00, - 0x13, - 0x03, - 0x03, - 0x8e, - 0x02, - 0x04, - 0x25, - 0x40, - 0x0b, - ]), - ]; - for (const msg of okayMessages) { - t.is(Message.isComplete(msg), true); // `${msg.toString("hex")} should be detected as complete` - } - - // truncated messages - const truncatedMessages = [ - undefined, - Buffer.from([]), - Buffer.from([0x01]), - Buffer.from([0x01, 0x09]), - Buffer.from([0x01, 0x09, 0x00]), - Buffer.from([ - 0x01, - 0x09, - 0x00, - 0x13, - 0x03, - 0x02, - 0x00, - 0x00, - 0x25, - 0x0b, - ]), - ]; - for (const msg of truncatedMessages) { - t.is(Message.isComplete(msg), false); // `${msg ? msg.toString("hex") : "null"} should be detected as incomplete` - } - - // faulty but non-truncated messages should be detected as complete - const faultyMessages = [ - Buffer.from([ - 0x01, - 0x09, - 0x00, - 0x13, - 0x03, - 0x02, - 0x00, - 0x00, - 0x25, - 0x0b, - 0xca, - ]), - Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), - Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), - Buffer.from([ - 0x01, - 0x0a, - 0x00, - 0x13, - 0x03, - 0x03, - 0x8e, - 0x02, - 0x04, - 0x25, - 0x40, - 0x0b, - ]), - ]; - for (const msg of faultyMessages) { - t.is(Message.isComplete(msg), true); // `${msg.toString("hex")} should be detected as complete` - } - - // actual messages from OZW, appended with some random data - const tooLongMessages = [ - Buffer.from([ - 0x01, - 0x09, - 0x00, - 0x13, - 0x03, - 0x02, - 0x00, - 0x00, - 0x25, - 0x0b, - 0xca, - 0x00, - ]), - Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99, 0x01, 0x02]), - Buffer.from([ - 0x01, - 0x06, - 0x00, - 0x46, - 0x0c, - 0x0d, - 0x32, - 0x8c, - 0xab, - 0xcd, - 0xef, - ]), - Buffer.from([ - 0x01, - 0x0a, - 0x00, - 0x13, - 0x03, - 0x03, - 0x8e, - 0x02, - 0x04, - 0x25, - 0x40, - 0x0b, - 0x12, - ]), - ]; - for (const msg of tooLongMessages) { - t.is(Message.isComplete(msg), true); // `${msg.toString("hex")} should be detected as complete` + assertZWaveError( + t, + () => Message.parse(message, {} as any), + { + messageMatches: msg, + errorCode: code, + }, + ); } }); 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 +110,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 +121,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 +133,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, @@ -302,32 +153,37 @@ test("toJSON() should return a semi-readable JSON representation", (t) => { t.deepEqual(msg4.toJSON(), json4); }); -test("getConstructor() should return `Message` for an unknown packet type", (t) => { +test("Parsing a buffer with an unknown function type returns an unspecified `Message` instance", (t) => { const unknown = Buffer.from([0x01, 0x03, 0x00, 0x00, 0xfc]); - t.is(Message.getConstructor(unknown), Message); + t.is( + Message.parse(unknown, {} as any).constructor, + Message, + ); }); 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 +193,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)); + (msg as any).nodeId = 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); + (msg as any).nodeId = 2; + t.is(msg.tryGetNode(host), undefined); }); diff --git a/packages/serial/src/message/Message.ts b/packages/serial/src/message/Message.ts index 1937cfbb78ce..5e17c08701c5 100644 --- a/packages/serial/src/message/Message.ts +++ b/packages/serial/src/message/Message.ts @@ -1,29 +1,31 @@ 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"; import { FunctionType, MessageType } from "./Constants"; -import { isNodeQuery } from "./INodeQuery"; +import { MessageHeaders } from "./MessageHeaders"; -export type MessageConstructor = new ( - host: ZWaveHost, - options?: MessageOptions, -) => T; - -export type DeserializingMessageConstructor = new ( - host: ZWaveHost, - options: MessageDeserializationOptions, -) => T; +export type MessageConstructor = typeof Message & { + new (options: MessageBaseOptions): T; +}; /** Where a serialized message originates from, to distinguish how certain messages need to be deserialized */ export enum MessageOrigin { @@ -31,31 +33,19 @@ export enum MessageOrigin { Host, } -export interface MessageDeserializationOptions { - data: Buffer; +export interface MessageParsingContext extends HostIDs, GetDeviceConfig { + /** How many bytes a node ID occupies in serial API commands */ + nodeIdType: NodeIDType; + sdkVersion: string | undefined; + requestStorage: Map> | undefined; origin?: MessageOrigin; - /** Whether CCs should be parsed immediately (only affects messages that contain CCs). Default: `true` */ - parseCCs?: boolean; - /** If known already, this contains the SDK version of the stick which can be used to interpret payloads differently */ - sdkVersion?: string; - /** Optional context used during deserialization */ - context?: unknown; -} - -/** - * Tests whether the given message constructor options contain a buffer for deserialization - */ -export function gotDeserializationOptions( - options: Record | undefined, -): options is MessageDeserializationOptions { - return options != undefined && Buffer.isBuffer(options.data); } export interface MessageBaseOptions { callbackId?: number; } -export interface MessageCreationOptions extends MessageBaseOptions { +export interface MessageOptions extends MessageBaseOptions { type?: MessageType; functionType?: FunctionType; expectedResponse?: FunctionType | typeof Message | ResponsePredicate; @@ -63,92 +53,162 @@ export interface MessageCreationOptions extends MessageBaseOptions { payload?: Buffer; } -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; +} + +export interface HasNodeId { + nodeId: number; +} + +/** Tests if the given message is for a node or references a node */ +export function hasNodeId(msg: T): msg is T & HasNodeId { + return typeof (msg as any).nodeId === "number"; +} + +/** Returns the number of bytes the first message in the buffer occupies */ +function getMessageLength(data: Buffer): number { + const remainingLength = data[1]; + return remainingLength + 2; +} + +export class MessageRaw { + public constructor( + public readonly type: MessageType, + public readonly functionType: FunctionType, + public readonly payload: Buffer, + ) {} + + public static parse(data: Buffer): MessageRaw { + // SOF, length, type, commandId and checksum must be present + if (!data.length || data.length < 5) { + throw new ZWaveError( + "Could not deserialize the message because it was truncated", + ZWaveErrorCodes.PacketFormat_Truncated, + ); + } + // the packet has to start with SOF + if (data[0] !== MessageHeaders.SOF) { + throw new ZWaveError( + "Could not deserialize the message because it does not start with SOF", + ZWaveErrorCodes.PacketFormat_Invalid, + ); + } + // check the length again, this time with the transmitted length + const messageLength = getMessageLength(data); + if (data.length < messageLength) { + throw new ZWaveError( + "Could not deserialize the message because it was truncated", + ZWaveErrorCodes.PacketFormat_Truncated, + ); + } + // check the checksum + const expectedChecksum = computeChecksum( + data.subarray(0, messageLength), + ); + if (data[messageLength - 1] !== expectedChecksum) { + throw new ZWaveError( + "Could not deserialize the message because the checksum didn't match", + ZWaveErrorCodes.PacketFormat_Checksum, + ); + } + + const type: MessageType = data[2]; + const functionType: FunctionType = data[3]; + const payloadLength = messageLength - 5; + const payload = data.subarray(4, 4 + payloadLength); + + return new MessageRaw(type, functionType, payload); + } + + public withPayload(payload: Buffer): MessageRaw { + return new MessageRaw(this.type, this.functionType, payload); + } +} /** * Represents a Z-Wave message for communication with the serial interface */ export class Message { public constructor( - public readonly host: ZWaveHost, options: MessageOptions = {}, ) { - // decide which implementation we follow - if (gotDeserializationOptions(options)) { - // #1: deserialize from payload - const payload = options.data; - - // SOF, length, type, commandId and checksum must be present - if (!payload.length || payload.length < 5) { - throw new ZWaveError( - "Could not deserialize the message because it was truncated", - ZWaveErrorCodes.PacketFormat_Truncated, - ); - } - // the packet has to start with SOF - if (payload[0] !== MessageHeaders.SOF) { - throw new ZWaveError( - "Could not deserialize the message because it does not start with SOF", - ZWaveErrorCodes.PacketFormat_Invalid, - ); - } - // check the length again, this time with the transmitted length - const messageLength = Message.getMessageLength(payload); - if (payload.length < messageLength) { - throw new ZWaveError( - "Could not deserialize the message because it was truncated", - ZWaveErrorCodes.PacketFormat_Truncated, - ); - } - // check the checksum - const expectedChecksum = computeChecksum( - payload.subarray(0, messageLength), + const { + // Try to determine the message type if none is given + type = getMessageType(this), + // Try to determine the function type if none is given + functionType = getFunctionType(this), + // Fall back to decorated response/callback types if none is given + expectedResponse = getExpectedResponse(this), + expectedCallback = getExpectedCallback(this), + payload = Buffer.allocUnsafe(0), + callbackId, + } = options; + + if (type == undefined) { + throw new ZWaveError( + "A message must have a given or predefined message type", + ZWaveErrorCodes.Argument_Invalid, ); - if (payload[messageLength - 1] !== expectedChecksum) { - throw new ZWaveError( - "Could not deserialize the message because the checksum didn't match", - ZWaveErrorCodes.PacketFormat_Checksum, - ); - } + } + if (functionType == undefined) { + throw new ZWaveError( + "A message must have a given or predefined function type", + ZWaveErrorCodes.Argument_Invalid, + ); + } - this.type = payload[2]; - this.functionType = payload[3]; - const payloadLength = messageLength - 5; - this.payload = payload.subarray(4, 4 + payloadLength); - } else { - // Try to determine the message type - if (options.type == undefined) options.type = getMessageType(this); - if (options.type == undefined) { - throw new ZWaveError( - "A message must have a given or predefined message type", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.type = options.type; + this.type = type; + this.functionType = functionType; + this.expectedResponse = expectedResponse; + this.expectedCallback = expectedCallback; + this.callbackId = callbackId; + this.payload = payload; + } - if (options.functionType == undefined) { - options.functionType = getFunctionType(this); - } - if (options.functionType == undefined) { - throw new ZWaveError( - "A message must have a given or predefined function type", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.functionType = options.functionType; + public static parse( + data: Buffer, + ctx: MessageParsingContext, + ): Message { + const raw = MessageRaw.parse(data); - // Fall back to decorated response/callback types if none is given - this.expectedResponse = options.expectedResponse - ?? getExpectedResponse(this); - this.expectedCallback = options.expectedCallback - ?? getExpectedCallback(this); + const Constructor = getMessageConstructor(raw.type, raw.functionType) + ?? Message; - this._callbackId = options.callbackId; + return Constructor.from(raw, ctx); + } - this.payload = options.payload || Buffer.allocUnsafe(0); - } + /** Creates an instance of the message that is serialized in the given buffer */ + public static from( + raw: MessageRaw, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ctx: MessageParsingContext, + ): Message { + return new this({ + type: raw.type, + functionType: raw.functionType, + payload: raw.payload, + }); } public type: MessageType; @@ -165,28 +225,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 +264,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 @@ -223,60 +279,6 @@ export class Message { return ret; } - /** Returns the number of bytes the first message in the buffer occupies */ - public static getMessageLength(data: Buffer): number { - const remainingLength = data[1]; - return remainingLength + 2; - } - - /** - * Checks if there's enough data in the buffer to deserialize - */ - public static isComplete(data?: Buffer): boolean { - if (!data || !data.length || data.length < 5) return false; // not yet - - const messageLength = Message.getMessageLength(data); - if (data.length < messageLength) return false; // not yet - - return true; // probably, but the checksum may be wrong - } - - /** - * Retrieves the correct constructor for the next message in the given Buffer. - * It is assumed that the buffer has been checked beforehand - */ - public static getConstructor(data: Buffer): MessageConstructor { - return getMessageConstructor(data[2], data[3]) || Message; - } - - /** Creates an instance of the message that is serialized in the given buffer */ - public static from( - host: ZWaveHost, - options: MessageDeserializationOptions, - contextStore?: Map>, - ): Message { - const Constructor = Message.getConstructor(options.data); - - // Take the context out of the context store if it exists - if (contextStore) { - const functionType = getFunctionTypeStatic(Constructor)!; - if (contextStore.has(functionType)) { - options.context = contextStore.get(functionType)!; - contextStore.delete(functionType); - } - } - - const ret = new Constructor(host, options); - return ret; - } - - /** Returns the slice of data which represents the message payload */ - public static extractPayload(data: Buffer): Buffer { - const messageLength = Message.getMessageLength(data); - const payloadLength = messageLength - 5; - return data.subarray(4, 4 + payloadLength); - } - /** Generates a representation of this Message for the log */ public toLogEntry(): MessageOrCCLogEntry { const tags = [ @@ -379,11 +381,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; } } @@ -403,18 +401,18 @@ export class Message { /** Finds the ID of the target or source node in a message, if it contains that information */ public getNodeId(): number | undefined { - if (isNodeQuery(this)) return this.nodeId; + if (hasNodeId(this)) return this.nodeId; // Override this in subclasses if a different behavior is desired } /** * 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/MessageHeaders.ts b/packages/serial/src/message/MessageHeaders.ts similarity index 100% rename from packages/serial/src/MessageHeaders.ts rename to packages/serial/src/message/MessageHeaders.ts 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/serial/src/MockSerialPort.ts b/packages/serial/src/mock/MockSerialPort.ts similarity index 95% rename from packages/serial/src/MockSerialPort.ts rename to packages/serial/src/mock/MockSerialPort.ts index 31654c80e839..f9ede7a3dd66 100644 --- a/packages/serial/src/MockSerialPort.ts +++ b/packages/serial/src/mock/MockSerialPort.ts @@ -5,13 +5,13 @@ import { Mixin } from "@zwave-js/shared"; import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import sinon from "sinon"; +import { ZWaveSerialPort } from "../serialport/ZWaveSerialPort"; +import type { ZWaveSerialPortEventCallbacks } from "../serialport/ZWaveSerialPortBase"; import { MockBinding as SerialPortMockBinding, type MockPortBinding as SerialPortMockPortBinding, } from "./SerialPortBindingMock"; import { SerialPortMock } from "./SerialPortMock"; -import { ZWaveSerialPort } from "./ZWaveSerialPort"; -import type { ZWaveSerialPortEventCallbacks } from "./ZWaveSerialPortBase"; const instances = new Map(); diff --git a/packages/serial/src/SerialPortBindingMock.ts b/packages/serial/src/mock/SerialPortBindingMock.ts similarity index 100% rename from packages/serial/src/SerialPortBindingMock.ts rename to packages/serial/src/mock/SerialPortBindingMock.ts diff --git a/packages/serial/src/SerialPortMock.ts b/packages/serial/src/mock/SerialPortMock.ts similarity index 89% rename from packages/serial/src/SerialPortMock.ts rename to packages/serial/src/mock/SerialPortMock.ts index c88a9fe47a77..e03d10d0d741 100644 --- a/packages/serial/src/SerialPortMock.ts +++ b/packages/serial/src/mock/SerialPortMock.ts @@ -17,8 +17,7 @@ export type SerialPortMockOpenOptions = Omit< >; export class SerialPortMock extends SerialPortStream { - // eslint-disable-next-line @typescript-eslint/unbound-method - static list = MockBinding.list; + // es tic list = MockBinding.list; static readonly binding = MockBinding; constructor( diff --git a/packages/serial/src/parsers/BootloaderParsers.ts b/packages/serial/src/parsers/BootloaderParsers.ts index c97d2be2efe2..0b7c5c0b47ba 100644 --- a/packages/serial/src/parsers/BootloaderParsers.ts +++ b/packages/serial/src/parsers/BootloaderParsers.ts @@ -1,6 +1,6 @@ import { Transform, type TransformCallback } from "node:stream"; -import type { SerialLogger } from "../Logger"; -import { XModemMessageHeaders } from "../MessageHeaders"; +import type { SerialLogger } from "../log/Logger"; +import { XModemMessageHeaders } from "../message/MessageHeaders"; export enum BootloaderChunkType { Error, diff --git a/packages/serial/src/parsers/SerialAPIParser.ts b/packages/serial/src/parsers/SerialAPIParser.ts index 80fbbbf31f9a..79653c6eda60 100644 --- a/packages/serial/src/parsers/SerialAPIParser.ts +++ b/packages/serial/src/parsers/SerialAPIParser.ts @@ -1,7 +1,7 @@ import { num2hex } from "@zwave-js/shared"; import { Transform, type TransformCallback } from "node:stream"; -import type { SerialLogger } from "../Logger"; -import { MessageHeaders } from "../MessageHeaders"; +import type { SerialLogger } from "../log/Logger"; +import { MessageHeaders } from "../message/MessageHeaders"; /** * Checks if there's enough data in the buffer to deserialize a complete message diff --git a/packages/serial/src/parsers/ZnifferParser.ts b/packages/serial/src/parsers/ZnifferParser.ts index fcc8e95b4a6b..5300bbaf88ad 100644 --- a/packages/serial/src/parsers/ZnifferParser.ts +++ b/packages/serial/src/parsers/ZnifferParser.ts @@ -1,7 +1,7 @@ import { Transform, type TransformCallback } from "node:stream"; -import type { SerialLogger } from "../Logger"; -import { ZnifferMessageHeaders } from "../MessageHeaders"; +import type { SerialLogger } from "../log/Logger"; import { ZnifferFrameType } from "../message/Constants"; +import { ZnifferMessageHeaders } from "../message/MessageHeaders"; /** Given a buffer that starts with SOF, this method returns the number of bytes the first message occupies in the buffer */ function getMessageLength(data: Buffer): number | undefined { diff --git a/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest._test.ts b/packages/serial/src/serialapi/application/ApplicationCommandRequest._test.ts similarity index 100% rename from packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest._test.ts rename to packages/serial/src/serialapi/application/ApplicationCommandRequest._test.ts diff --git a/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts new file mode 100644 index 000000000000..bcdf0f7fecbb --- /dev/null +++ b/packages/serial/src/serialapi/application/ApplicationCommandRequest.ts @@ -0,0 +1,203 @@ +import { type CommandClass } from "@zwave-js/cc"; +import { + type FrameType, + type MessageOrCCLogEntry, + MessagePriority, + type MessageRecord, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, + parseNodeID, +} from "@zwave-js/core"; +import { type CCEncodingContext } from "@zwave-js/host"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { type MessageWithCC } from "../utils"; + +export enum ApplicationCommandStatusFlags { + RoutedBusy = 0b1, // A response route is locked by the application + LowPower = 0b10, // Received at low output power level + + TypeSingle = 0b0000, // Received a single cast frame + TypeBroad = 0b0100, // Received a broad cast frame + TypeMulti = 0b1000, // Received a multi cast frame + TypeMask = TypeSingle | TypeBroad | TypeMulti, + + Explore = 0b10000, // Received an explore frame + + ForeignFrame = 0b0100_0000, // Received a foreign frame (only promiscuous mode) + ForeignHomeId = 0b1000_0000, // The received frame is received from a foreign HomeID. Only Controllers in Smart Start AddNode mode can receive this status. +} + +export type ApplicationCommandRequestOptions = + & ( + | { command: CommandClass } + | { + nodeId: number; + serializedCC: Buffer; + } + ) + & { + frameType?: ApplicationCommandRequest["frameType"]; + routedBusy?: boolean; + isExploreFrame?: boolean; + isForeignFrame?: boolean; + fromForeignHomeId?: boolean; + }; + +@messageTypes(MessageType.Request, FunctionType.ApplicationCommand) +// This does not expect a response. The controller sends us this when a node sends a command +@priority(MessagePriority.Normal) +export class ApplicationCommandRequest extends Message + implements MessageWithCC +{ + public constructor( + options: ApplicationCommandRequestOptions & MessageBaseOptions, + ) { + super(options); + + if ("command" in options) { + this.command = options.command; + } else { + this._nodeId = options.nodeId; + this.serializedCC = options.serializedCC; + } + + this.frameType = options.frameType ?? "singlecast"; + this.routedBusy = options.routedBusy ?? false; + this.isExploreFrame = options.isExploreFrame ?? false; + this.isForeignFrame = options.isForeignFrame ?? false; + this.fromForeignHomeId = options.fromForeignHomeId ?? false; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationCommandRequest { + // first byte is a status flag + const status = raw.payload[0]; + const routedBusy = !!( + status & ApplicationCommandStatusFlags.RoutedBusy + ); + let frameType: FrameType; + + switch (status & ApplicationCommandStatusFlags.TypeMask) { + case ApplicationCommandStatusFlags.TypeMulti: + frameType = "multicast"; + break; + case ApplicationCommandStatusFlags.TypeBroad: + frameType = "broadcast"; + break; + default: + frameType = "singlecast"; + } + const isExploreFrame: boolean = frameType === "broadcast" + && !!(status & ApplicationCommandStatusFlags.Explore); + const isForeignFrame = !!( + status & ApplicationCommandStatusFlags.ForeignFrame + ); + const fromForeignHomeId = !!( + status & ApplicationCommandStatusFlags.ForeignHomeId + ); + + // followed by a node ID + let offset = 1; + const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( + raw.payload, + ctx.nodeIdType, + offset, + ); + offset += nodeIdBytes; + // and a command class + const commandLength = raw.payload[offset++]; + const serializedCC = raw.payload.subarray( + offset, + offset + commandLength, + ); + + return new this({ + routedBusy, + frameType, + isExploreFrame, + isForeignFrame, + fromForeignHomeId, + serializedCC, + nodeId, + }); + } + + public readonly routedBusy: boolean; + public readonly frameType: FrameType; + public readonly isExploreFrame: boolean; + public readonly isForeignFrame: boolean; + public readonly fromForeignHomeId: boolean; + + // This needs to be writable or unwrapping MultiChannelCCs crashes + public command: CommandClass | undefined; + + private _nodeId: number | undefined; + public override getNodeId(): number | undefined { + if (this.command?.isSinglecast()) { + return this.command.nodeId; + } + + return this._nodeId ?? super.getNodeId(); + } + + public serializedCC: Buffer | undefined; + public serializeCC(ctx: CCEncodingContext): Buffer { + if (!this.serializedCC) { + if (!this.command) { + throw new ZWaveError( + `Cannot serialize a ${this.constructor.name} without a command`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.serializedCC = this.command.serialize(ctx); + } + return this.serializedCC; + } + + public serialize(ctx: MessageEncodingContext): Buffer { + const statusByte = (this.frameType === "broadcast" + ? ApplicationCommandStatusFlags.TypeBroad + : this.frameType === "multicast" + ? ApplicationCommandStatusFlags.TypeMulti + : 0) + | (this.routedBusy ? ApplicationCommandStatusFlags.RoutedBusy : 0); + + const serializedCC = this.serializeCC(ctx); + const nodeId = encodeNodeID( + this.getNodeId() ?? ctx.ownNodeId, + ctx.nodeIdType, + ); + this.payload = Buffer.concat([ + Buffer.from([statusByte]), + nodeId, + Buffer.from([serializedCC.length]), + serializedCC, + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + const message: MessageRecord = {}; + if (this.frameType !== "singlecast") { + message.type = this.frameType; + } + return { + ...super.toLogEntry(), + message, + }; + } +} diff --git a/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts b/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts similarity index 54% rename from packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts rename to packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts index c528d7408735..1f9b5b32d87b 100644 --- a/packages/zwave-js/src/lib/serialapi/application/ApplicationUpdateRequest.ts +++ b/packages/serial/src/serialapi/application/ApplicationUpdateRequest.ts @@ -11,17 +11,16 @@ import { parseNodeID, parseNodeUpdatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { - type DeserializingMessageConstructor, FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + type MessageConstructor, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, - gotDeserializationOptions, messageTypes, } from "@zwave-js/serial"; import { buffer2hex, getEnumMemberName } from "@zwave-js/shared"; @@ -46,50 +45,62 @@ const { } = createSimpleReflectionDecorator< ApplicationUpdateRequest, [updateType: ApplicationUpdateTypes], - DeserializingMessageConstructor + MessageConstructor >({ name: "applicationUpdateType", }); +export interface ApplicationUpdateRequestOptions { + updateType?: ApplicationUpdateTypes; +} + @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); - - if (gotDeserializationOptions(options)) { - this.updateType = this.payload[0]; - - const CommandConstructor = getApplicationUpdateRequestConstructor( - this.updateType, - ); - if ( - CommandConstructor - && (new.target as any) !== CommandConstructor - ) { - return new CommandConstructor(host, options); - } - - this.payload = this.payload.subarray(1); - } else { - this.updateType = getApplicationUpdateType(this)!; + public constructor( + options: ApplicationUpdateRequestOptions & MessageBaseOptions = {}, + ) { + super(options); + + this.updateType = options.updateType ?? getApplicationUpdateType(this)!; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationUpdateRequest { + const updateType: ApplicationUpdateTypes = raw.payload[0]; + const payload = raw.payload.subarray(1); + + const CommandConstructor = getApplicationUpdateRequestConstructor( + updateType, + ); + if (CommandConstructor) { + return CommandConstructor.from( + raw.withPayload(payload), + ctx, + ) as ApplicationUpdateRequest; } + + const ret = new ApplicationUpdateRequest({ + updateType, + }); + ret.payload = payload; + return ret; } 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); } } -interface ApplicationUpdateRequestWithNodeInfoOptions - extends MessageBaseOptions -{ +export interface ApplicationUpdateRequestWithNodeInfoOptions { nodeInformation: NodeUpdatePayload; } @@ -97,34 +108,39 @@ export class ApplicationUpdateRequestWithNodeInfo extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | ApplicationUpdateRequestWithNodeInfoOptions, + & ApplicationUpdateRequestWithNodeInfoOptions + & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.nodeInformation = parseNodeUpdatePayload( - this.payload, - this.host.nodeIdType, - ); - this.nodeId = this.nodeInformation.nodeId; - } else { - this.nodeId = options.nodeInformation.nodeId; - this.nodeInformation = options.nodeInformation; - } + super(options); + + this.nodeId = options.nodeInformation.nodeId; + this.nodeInformation = options.nodeInformation; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationUpdateRequestWithNodeInfo { + const nodeInformation: NodeUpdatePayload = parseNodeUpdatePayload( + raw.payload, + ctx.nodeIdType, + ); + + return new this({ + nodeInformation, + }); } 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); } } @@ -148,54 +164,97 @@ export class ApplicationUpdateRequestNodeAdded extends ApplicationUpdateRequestWithNodeInfo {} +export interface ApplicationUpdateRequestNodeRemovedOptions { + nodeId: number; +} + @applicationUpdateType(ApplicationUpdateTypes.Node_Removed) export class ApplicationUpdateRequestNodeRemoved extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & ApplicationUpdateRequestNodeRemovedOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); + this.nodeId = options.nodeId; + } - const { nodeId } = parseNodeID(this.payload, host.nodeIdType, 0); - this.nodeId = nodeId; + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationUpdateRequestNodeRemoved { + const { nodeId } = parseNodeID(raw.payload, ctx.nodeIdType, 0); // byte 1/2 is 0, meaning unknown + + return new this({ + nodeId, + }); } public nodeId: number; } +export interface ApplicationUpdateRequestSmartStartHomeIDReceivedBaseOptions { + remoteNodeId: number; + nwiHomeId: Buffer; + basicDeviceClass: BasicDeviceClass; + genericDeviceClass: number; + specificDeviceClass: number; + supportedCCs: CommandClasses[]; +} + class ApplicationUpdateRequestSmartStartHomeIDReceivedBase extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & ApplicationUpdateRequestSmartStartHomeIDReceivedBaseOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.remoteNodeId = options.remoteNodeId; + this.nwiHomeId = options.nwiHomeId; + this.basicDeviceClass = options.basicDeviceClass; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.supportedCCs = options.supportedCCs; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationUpdateRequestSmartStartHomeIDReceivedBase { let offset = 0; - const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, + const { nodeId: remoteNodeId, bytesRead: nodeIdBytes } = parseNodeID( + raw.payload, + ctx.nodeIdType, offset, ); offset += nodeIdBytes; - this.remoteNodeId = nodeId; - // next byte is rxStatus offset++; - - this.nwiHomeId = this.payload.subarray(offset, offset + 4); + const nwiHomeId: Buffer = raw.payload.subarray(offset, offset + 4); offset += 4; - - const ccLength = this.payload[offset++]; - this.basicDeviceClass = this.payload[offset++]; - this.genericDeviceClass = this.payload[offset++]; - this.specificDeviceClass = this.payload[offset++]; - this.supportedCCs = parseCCList( - this.payload.subarray(offset, offset + ccLength), + const ccLength = raw.payload[offset++]; + const basicDeviceClass: BasicDeviceClass = raw.payload[offset++]; + const genericDeviceClass = raw.payload[offset++]; + const specificDeviceClass = raw.payload[offset++]; + const supportedCCs = parseCCList( + raw.payload.subarray(offset, offset + ccLength), ).supportedCCs; + + return new this({ + remoteNodeId, + nwiHomeId, + basicDeviceClass, + genericDeviceClass, + specificDeviceClass, + supportedCCs, + }); } public readonly remoteNodeId: number; @@ -240,19 +299,38 @@ export class ApplicationUpdateRequestSmartStartLongRangeHomeIDReceived extends ApplicationUpdateRequestSmartStartHomeIDReceivedBase {} +export interface ApplicationUpdateRequestSUCIdChangedOptions { + sucNodeID: number; +} + @applicationUpdateType(ApplicationUpdateTypes.SUC_IdChanged) export class ApplicationUpdateRequestSUCIdChanged extends ApplicationUpdateRequest { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & ApplicationUpdateRequestSUCIdChangedOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); - const { nodeId } = parseNodeID(this.payload, host.nodeIdType, 0); - this.sucNodeID = nodeId; + this.sucNodeID = options.sucNodeID; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ApplicationUpdateRequestSUCIdChanged { + const { nodeId: sucNodeID } = parseNodeID( + raw.payload, + ctx.nodeIdType, + 0, + ); // byte 1/2 is 0, meaning unknown + + return new this({ + sucNodeID, + }); } public sucNodeID: number; diff --git a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts similarity index 68% rename from packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts rename to packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.test.ts index 859512358209..87ace9ef00d6 100644 --- a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.test.ts +++ b/packages/serial/src/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, { - data: Buffer.from( + Message.parse( + Buffer.from( "011200a80001020a320221340000000000000069", "hex", ), - }) + {} as any, + ) ); }); diff --git a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts similarity index 51% rename from packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts rename to packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts index 98ef572cc3b7..3b81f34a557c 100644 --- a/packages/zwave-js/src/lib/serialapi/application/BridgeApplicationCommandRequest.ts +++ b/packages/serial/src/serialapi/application/BridgeApplicationCommandRequest.ts @@ -1,4 +1,4 @@ -import { CommandClass, type ICommandClassContainer } from "@zwave-js/cc"; +import { type CommandClass } from "@zwave-js/cc"; import { type FrameType, type MessageOrCCLogEntry, @@ -8,95 +8,149 @@ import { NODE_ID_BROADCAST_LR, type RSSI, RssiError, - type SinglecastCC, isLongRangeNodeId, parseNodeBitMask, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, - type MessageDeserializationOptions, + type MessageBaseOptions, + type MessageParsingContext, + type MessageRaw, MessageType, messageTypes, priority, } from "@zwave-js/serial"; import { getEnumMemberName } from "@zwave-js/shared"; import { tryParseRSSI } from "../transport/SendDataShared"; +import { type MessageWithCC } from "../utils"; import { ApplicationCommandStatusFlags } from "./ApplicationCommandRequest"; +export type BridgeApplicationCommandRequestOptions = + & ( + | { command: CommandClass } + | { + nodeId: number; + serializedCC: Buffer; + } + ) + & { + routedBusy: boolean; + frameType: FrameType; + isExploreFrame: boolean; + isForeignFrame: boolean; + fromForeignHomeId: boolean; + ownNodeId: number; + targetNodeId: number | number[]; + rssi?: number; + }; + @messageTypes(MessageType.Request, FunctionType.BridgeApplicationCommand) // This does not expect a response. The controller sends us this when a node sends a command @priority(MessagePriority.Normal) export class BridgeApplicationCommandRequest extends Message - implements ICommandClassContainer + implements MessageWithCC { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: BridgeApplicationCommandRequestOptions & MessageBaseOptions, ) { - super(host, options); - // if (gotDeserializationOptions(options)) { + super(options); + + if ("command" in options) { + this.command = options.command; + } else { + this._nodeId = options.nodeId; + this.serializedCC = options.serializedCC; + } + + this.routedBusy = options.routedBusy; + this.frameType = options.frameType; + this.isExploreFrame = options.isExploreFrame; + this.isForeignFrame = options.isForeignFrame; + this.fromForeignHomeId = options.fromForeignHomeId; + // FIXME: We only need this in the toLogEntry context + this.ownNodeId = options.ownNodeId; + this.targetNodeId = options.targetNodeId; + this.rssi = options.rssi; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): BridgeApplicationCommandRequest { // first byte is a status flag - const status = this.payload[0]; - this.routedBusy = !!(status & ApplicationCommandStatusFlags.RoutedBusy); + const status = raw.payload[0]; + const routedBusy = + !!(status & ApplicationCommandStatusFlags.RoutedBusy); + let frameType: FrameType; switch (status & ApplicationCommandStatusFlags.TypeMask) { case ApplicationCommandStatusFlags.TypeMulti: - this.frameType = "multicast"; + frameType = "multicast"; break; case ApplicationCommandStatusFlags.TypeBroad: - this.frameType = "broadcast"; + frameType = "broadcast"; break; default: - this.frameType = "singlecast"; + frameType = "singlecast"; } - this.isExploreFrame = this.frameType === "broadcast" + + const isExploreFrame = frameType === "broadcast" && !!(status & ApplicationCommandStatusFlags.Explore); - this.isForeignFrame = !!( + const isForeignFrame = !!( status & ApplicationCommandStatusFlags.ForeignFrame ); - this.fromForeignHomeId = !!( + const fromForeignHomeId = !!( status & ApplicationCommandStatusFlags.ForeignHomeId ); - let offset = 1; const { nodeId: destinationNodeId, bytesRead: dstNodeIdBytes } = - parseNodeID(this.payload, host.nodeIdType, offset); + parseNodeID(raw.payload, ctx.nodeIdType, offset); offset += dstNodeIdBytes; const { nodeId: sourceNodeId, bytesRead: srcNodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, + raw.payload, + ctx.nodeIdType, offset, ); offset += srcNodeIdBytes; - // Parse the CC - const commandLength = this.payload[offset++]; - this.command = CommandClass.from(this.host, { - data: this.payload.subarray(offset, offset + commandLength), - nodeId: sourceNodeId, - origin: options.origin, - frameType: this.frameType, - }) as SinglecastCC; + // Extract the CC payload + const commandLength = raw.payload[offset++]; + const serializedCC = raw.payload.subarray( + offset, + offset + commandLength, + ); offset += commandLength; - // Read the correct target node id - const multicastNodesLength = this.payload[offset]; + const multicastNodesLength = raw.payload[offset]; offset++; - if (this.frameType === "multicast") { - this.targetNodeId = parseNodeBitMask( - this.payload.subarray(offset, offset + multicastNodesLength), + let targetNodeId: number | number[]; + if (frameType === "multicast") { + targetNodeId = parseNodeBitMask( + raw.payload.subarray(offset, offset + multicastNodesLength), ); - } else if (this.frameType === "singlecast") { - this.targetNodeId = destinationNodeId; + } else if (frameType === "singlecast") { + targetNodeId = destinationNodeId; } else { - this.targetNodeId = isLongRangeNodeId(sourceNodeId) + targetNodeId = isLongRangeNodeId(sourceNodeId) ? NODE_ID_BROADCAST_LR : NODE_ID_BROADCAST; } + offset += multicastNodesLength; + const rssi: number | undefined = tryParseRSSI(raw.payload, offset); - this.rssi = tryParseRSSI(this.payload, offset); + return new this({ + routedBusy, + frameType, + isExploreFrame, + isForeignFrame, + fromForeignHomeId, + nodeId: sourceNodeId, + serializedCC, + ownNodeId: ctx.ownNodeId, + targetNodeId, + rssi, + }); } public readonly routedBusy: boolean; @@ -107,14 +161,19 @@ export class BridgeApplicationCommandRequest extends Message public readonly fromForeignHomeId: boolean; public readonly rssi?: RSSI; + public readonly ownNodeId: number; + + public readonly serializedCC: Buffer | undefined; + // This needs to be writable or unwrapping MultiChannelCCs crashes - public command: SinglecastCC; // TODO: why is this a SinglecastCC? + public command: CommandClass | undefined; + private _nodeId: number | undefined; public override getNodeId(): number | undefined { - if (this.command.isSinglecast()) { + if (this.command?.isSinglecast()) { return this.command.nodeId; } - return super.getNodeId(); + return this._nodeId ?? super.getNodeId(); } public toLogEntry(): MessageOrCCLogEntry { @@ -122,7 +181,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/serial/src/serialapi/application/SerialAPIStartedRequest.ts similarity index 68% rename from packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts rename to packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts index 3fffcdbd9b5b..a432ffd8fdd3 100644 --- a/packages/zwave-js/src/lib/serialapi/application/SerialAPIStartedRequest.ts +++ b/packages/serial/src/serialapi/application/SerialAPIStartedRequest.ts @@ -5,14 +5,13 @@ import { encodeCCList, parseCCList, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageRaw, MessageType, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -43,7 +42,7 @@ export enum SerialAPIWakeUpReason { Unknown = 0xff, } -export interface SerialAPIStartedRequestOptions extends MessageBaseOptions { +export interface SerialAPIStartedRequestOptions { wakeUpReason: SerialAPIWakeUpReason; watchdogEnabled: boolean; genericDeviceClass: number; @@ -59,43 +58,54 @@ export interface SerialAPIStartedRequestOptions extends MessageBaseOptions { @priority(MessagePriority.Normal) export class SerialAPIStartedRequest extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SerialAPIStartedRequestOptions, + options: SerialAPIStartedRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - this.wakeUpReason = this.payload[0]; - this.watchdogEnabled = this.payload[1] === 0x01; - - const deviceOption = this.payload[2]; - this.isListening = !!(deviceOption & 0b10_000_000); + this.wakeUpReason = options.wakeUpReason; + this.watchdogEnabled = options.watchdogEnabled; + this.isListening = options.isListening; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + this.supportedCCs = options.supportedCCs; + this.controlledCCs = options.controlledCCs; + this.supportsLongRange = options.supportsLongRange; + } - this.genericDeviceClass = this.payload[3]; - this.specificDeviceClass = this.payload[4]; + public static from( + raw: MessageRaw, + ): SerialAPIStartedRequest { + const wakeUpReason: SerialAPIWakeUpReason = raw.payload[0]; + const watchdogEnabled = raw.payload[1] === 0x01; + const deviceOption = raw.payload[2]; + const isListening = !!(deviceOption & 0b10_000_000); + const genericDeviceClass = raw.payload[3]; + const specificDeviceClass = raw.payload[4]; - // Parse list of CCs - const numCCBytes = this.payload[5]; - const ccBytes = this.payload.subarray(6, 6 + numCCBytes); - const ccList = parseCCList(ccBytes); - this.supportedCCs = ccList.supportedCCs; - this.controlledCCs = ccList.controlledCCs; + // Parse list of CCs + const numCCBytes = raw.payload[5]; + const ccBytes = raw.payload.subarray(6, 6 + numCCBytes); + const ccList = parseCCList(ccBytes); + const supportedCCs: CommandClasses[] = ccList.supportedCCs; + const controlledCCs: CommandClasses[] = ccList.controlledCCs; - // Parse supported protocols - if (this.payload.length >= 6 + numCCBytes + 1) { - const protocols = this.payload[6 + numCCBytes]; - this.supportsLongRange = !!(protocols & 0b1); - } - } else { - this.wakeUpReason = options.wakeUpReason; - this.watchdogEnabled = options.watchdogEnabled; - this.isListening = options.isListening; - this.genericDeviceClass = options.genericDeviceClass; - this.specificDeviceClass = options.specificDeviceClass; - this.supportedCCs = options.supportedCCs; - this.controlledCCs = options.controlledCCs; - this.supportsLongRange = options.supportsLongRange; + // Parse supported protocols + let supportsLongRange = false; + if (raw.payload.length >= 6 + numCCBytes + 1) { + const protocols = raw.payload[6 + numCCBytes]; + supportsLongRange = !!(protocols & 0b1); } + + return new this({ + wakeUpReason, + watchdogEnabled, + isListening, + genericDeviceClass, + specificDeviceClass, + supportedCCs, + controlledCCs, + supportsLongRange, + }); } public wakeUpReason: SerialAPIWakeUpReason; @@ -108,7 +118,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 +132,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/serial/src/serialapi/application/ShutdownMessages.ts similarity index 60% rename from packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts rename to packages/serial/src/serialapi/application/ShutdownMessages.ts index ee6fd3212a26..87872a441381 100644 --- a/packages/zwave-js/src/lib/serialapi/application/ShutdownMessages.ts +++ b/packages/serial/src/serialapi/application/ShutdownMessages.ts @@ -1,17 +1,17 @@ import { type MessageOrCCLogEntry, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, messageTypes, priority, } from "@zwave-js/serial"; -export interface ShutdownRequestOptions extends MessageBaseOptions { +export interface ShutdownRequestOptions { someProperty: number; } @@ -20,14 +20,30 @@ export interface ShutdownRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.Shutdown) export class ShutdownRequest extends Message {} +export interface ShutdownResponseOptions { + success: boolean; +} + @messageTypes(MessageType.Response, FunctionType.Shutdown) export class ShutdownResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ShutdownResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ShutdownResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } public readonly success: boolean; diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts b/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts similarity index 50% rename from packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts rename to packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts index 3cf9a6370e8a..7e0fdbe10880 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetControllerCapabilitiesMessages.ts +++ b/packages/serial/src/serialapi/capability/GetControllerCapabilitiesMessages.ts @@ -1,13 +1,13 @@ import { ControllerCapabilityFlags, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -17,9 +17,7 @@ import { @priority(MessagePriority.Controller) export class GetControllerCapabilitiesRequest extends Message {} -export interface GetControllerCapabilitiesResponseOptions - extends MessageBaseOptions -{ +export interface GetControllerCapabilitiesResponseOptions { isSecondary: boolean; isUsingHomeIdFromOtherNetwork: boolean; isSISPresent: boolean; @@ -31,42 +29,51 @@ export interface GetControllerCapabilitiesResponseOptions @messageTypes(MessageType.Response, FunctionType.GetControllerCapabilities) export class GetControllerCapabilitiesResponse extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetControllerCapabilitiesResponseOptions, + options: GetControllerCapabilitiesResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - const capabilityFlags = this.payload[0]; - this.isSecondary = !!( - capabilityFlags & ControllerCapabilityFlags.Secondary - ); - this.isUsingHomeIdFromOtherNetwork = !!( - capabilityFlags & ControllerCapabilityFlags.OnOtherNetwork - ); - this.isSISPresent = !!( - capabilityFlags & ControllerCapabilityFlags.SISPresent - ); - this.wasRealPrimary = !!( - capabilityFlags & ControllerCapabilityFlags.WasRealPrimary - ); - this.isStaticUpdateController = !!( - capabilityFlags & ControllerCapabilityFlags.SUC - ); - this.noNodesIncluded = !!( - capabilityFlags & ControllerCapabilityFlags.NoNodesIncluded - ); - } else { - this.isSecondary = options.isSecondary; - this.isUsingHomeIdFromOtherNetwork = - options.isUsingHomeIdFromOtherNetwork; - this.isSISPresent = options.isSISPresent; - this.wasRealPrimary = options.wasRealPrimary; - this.isStaticUpdateController = options.isStaticUpdateController; - this.noNodesIncluded = options.noNodesIncluded; - } + this.isSecondary = options.isSecondary; + this.isUsingHomeIdFromOtherNetwork = + options.isUsingHomeIdFromOtherNetwork; + this.isSISPresent = options.isSISPresent; + this.wasRealPrimary = options.wasRealPrimary; + this.isStaticUpdateController = options.isStaticUpdateController; + this.noNodesIncluded = options.noNodesIncluded; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetControllerCapabilitiesResponse { + const capabilityFlags = raw.payload[0]; + const isSecondary = !!( + capabilityFlags & ControllerCapabilityFlags.Secondary + ); + const isUsingHomeIdFromOtherNetwork = !!( + capabilityFlags & ControllerCapabilityFlags.OnOtherNetwork + ); + const isSISPresent = !!( + capabilityFlags & ControllerCapabilityFlags.SISPresent + ); + const wasRealPrimary = !!( + capabilityFlags & ControllerCapabilityFlags.WasRealPrimary + ); + const isStaticUpdateController = !!( + capabilityFlags & ControllerCapabilityFlags.SUC + ); + const noNodesIncluded = !!( + capabilityFlags & ControllerCapabilityFlags.NoNodesIncluded + ); + + return new this({ + isSecondary, + isUsingHomeIdFromOtherNetwork, + isSISPresent, + wasRealPrimary, + isStaticUpdateController, + noNodesIncluded, + }); } public isSecondary: boolean; @@ -76,7 +83,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 +100,6 @@ export class GetControllerCapabilitiesResponse extends Message { ? ControllerCapabilityFlags.NoNodesIncluded : 0), ]); - return super.serialize(); + return super.serialize(ctx); } } diff --git a/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts b/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts new file mode 100644 index 000000000000..ec5a00dfd39b --- /dev/null +++ b/packages/serial/src/serialapi/capability/GetControllerVersionMessages.ts @@ -0,0 +1,63 @@ +import { MessagePriority, type ZWaveLibraryTypes } from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { cpp2js } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.GetControllerVersion) +@expectedResponse(FunctionType.GetControllerVersion) +@priority(MessagePriority.Controller) +export class GetControllerVersionRequest extends Message {} + +export interface GetControllerVersionResponseOptions { + controllerType: ZWaveLibraryTypes; + libraryVersion: string; +} + +@messageTypes(MessageType.Response, FunctionType.GetControllerVersion) +export class GetControllerVersionResponse extends Message { + public constructor( + options: GetControllerVersionResponseOptions & MessageBaseOptions, + ) { + super(options); + + this.controllerType = options.controllerType; + this.libraryVersion = options.libraryVersion; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetControllerVersionResponse { + // The payload consists of a zero-terminated string and a uint8 for the controller type + const libraryVersion = cpp2js(raw.payload.toString("ascii")); + const controllerType: ZWaveLibraryTypes = + raw.payload[libraryVersion.length + 1]; + + return new this({ + libraryVersion, + controllerType, + }); + } + + public controllerType: ZWaveLibraryTypes; + public libraryVersion: string; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.concat([ + Buffer.from(`${this.libraryVersion}\0`, "ascii"), + Buffer.from([this.controllerType]), + ]); + + return super.serialize(ctx); + } +} diff --git a/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts b/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts new file mode 100644 index 000000000000..5424ecfb865b --- /dev/null +++ b/packages/serial/src/serialapi/capability/GetLongRangeNodesMessages.ts @@ -0,0 +1,130 @@ +import { + MessagePriority, + NUM_LR_NODEMASK_SEGMENT_BYTES, + NUM_LR_NODES_PER_SEGMENT, + encodeLongRangeNodeBitMask, + parseLongRangeNodeBitMask, +} from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; + +function getFirstNodeId(segmentNumber: number): number { + return 256 + NUM_LR_NODES_PER_SEGMENT * segmentNumber; +} + +export interface GetLongRangeNodesRequestOptions { + segmentNumber: number; +} + +@messageTypes(MessageType.Request, FunctionType.GetLongRangeNodes) +@expectedResponse(FunctionType.GetLongRangeNodes) +@priority(MessagePriority.Controller) +export class GetLongRangeNodesRequest extends Message { + public constructor( + options: GetLongRangeNodesRequestOptions & MessageBaseOptions, + ) { + super(options); + + this.segmentNumber = options.segmentNumber; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetLongRangeNodesRequest { + const segmentNumber = raw.payload[0]; + + return new this({ + segmentNumber, + }); + } + + public segmentNumber: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.segmentNumber]); + return super.serialize(ctx); + } +} + +export interface GetLongRangeNodesResponseOptions { + moreNodes: boolean; + segmentNumber: number; + nodeIds: number[]; +} + +@messageTypes(MessageType.Response, FunctionType.GetLongRangeNodes) +export class GetLongRangeNodesResponse extends Message { + public constructor( + options: GetLongRangeNodesResponseOptions & MessageBaseOptions, + ) { + super(options); + + this.moreNodes = options.moreNodes; + this.segmentNumber = options.segmentNumber; + this.nodeIds = options.nodeIds; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetLongRangeNodesResponse { + const moreNodes: boolean = raw.payload[0] != 0; + const segmentNumber = raw.payload[1]; + const listLength = raw.payload[2]; + + const listStart = 3; + const listEnd = listStart + listLength; + let nodeIds: number[]; + if (listEnd <= raw.payload.length) { + const nodeBitMask = raw.payload.subarray( + listStart, + listEnd, + ); + nodeIds = parseLongRangeNodeBitMask( + nodeBitMask, + getFirstNodeId(segmentNumber), + ); + } else { + nodeIds = []; + } + + return new this({ + moreNodes, + segmentNumber, + nodeIds, + }); + } + + public moreNodes: boolean; + public segmentNumber: number; + public nodeIds: readonly number[]; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.allocUnsafe( + 3 + NUM_LR_NODEMASK_SEGMENT_BYTES, + ); + + this.payload[0] = this.moreNodes ? 1 : 0; + this.payload[1] = this.segmentNumber; + this.payload[2] = NUM_LR_NODEMASK_SEGMENT_BYTES; + + const nodeBitMask = encodeLongRangeNodeBitMask( + this.nodeIds, + getFirstNodeId(this.segmentNumber), + ); + nodeBitMask.copy(this.payload, 3); + + return super.serialize(ctx); + } +} diff --git a/packages/serial/src/serialapi/capability/GetProtocolVersionMessages.ts b/packages/serial/src/serialapi/capability/GetProtocolVersionMessages.ts new file mode 100644 index 000000000000..0a15618c40bc --- /dev/null +++ b/packages/serial/src/serialapi/capability/GetProtocolVersionMessages.ts @@ -0,0 +1,78 @@ +import type { ProtocolType } from "@zwave-js/core"; +import { MessagePriority } from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; + +@messageTypes(MessageType.Request, FunctionType.GetProtocolVersion) +@priority(MessagePriority.Controller) +@expectedResponse(FunctionType.GetProtocolVersion) +export class GetProtocolVersionRequest extends Message {} + +export interface GetProtocolVersionResponseOptions { + protocolType: ProtocolType; + protocolVersion: string; + applicationFrameworkBuildNumber?: number; + gitCommitHash?: string; +} + +@messageTypes(MessageType.Response, FunctionType.GetProtocolVersion) +export class GetProtocolVersionResponse extends Message { + public constructor( + options: GetProtocolVersionResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.protocolType = options.protocolType; + this.protocolVersion = options.protocolVersion; + this.applicationFrameworkBuildNumber = + options.applicationFrameworkBuildNumber; + this.gitCommitHash = options.gitCommitHash; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetProtocolVersionResponse { + const protocolType: ProtocolType = raw.payload[0]; + const protocolVersion = [ + raw.payload[1], + raw.payload[2], + raw.payload[3], + ].join("."); + let applicationFrameworkBuildNumber: number | undefined; + if (raw.payload.length >= 6) { + const appBuild = raw.payload.readUInt16BE(4); + if (appBuild !== 0) applicationFrameworkBuildNumber = appBuild; + } + + let gitCommitHash: string | undefined; + if (raw.payload.length >= 22) { + const commitHash = raw.payload.subarray(6, 22); + if (!commitHash.every((b) => b === 0)) { + gitCommitHash = commitHash.toString("hex"); + } + } + + return new this({ + protocolType, + protocolVersion, + applicationFrameworkBuildNumber, + gitCommitHash, + }); + } + + public readonly protocolType: ProtocolType; + public readonly protocolVersion: string; + public readonly applicationFrameworkBuildNumber?: number; + public readonly gitCommitHash?: string; +} diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts b/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts similarity index 53% rename from packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts rename to packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts index dbf295ca63d6..45f749ec072c 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiCapabilitiesMessages.ts +++ b/packages/serial/src/serialapi/capability/GetSerialApiCapabilitiesMessages.ts @@ -1,13 +1,13 @@ import { MessagePriority, encodeBitMask, parseBitMask } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -20,9 +20,7 @@ const NUM_FUNCTION_BYTES = NUM_FUNCTIONS / 8; @priority(MessagePriority.Controller) export class GetSerialApiCapabilitiesRequest extends Message {} -export interface GetSerialApiCapabilitiesResponseOptions - extends MessageBaseOptions -{ +export interface GetSerialApiCapabilitiesResponseOptions { firmwareVersion: string; manufacturerId: number; productType: number; @@ -33,32 +31,43 @@ export interface GetSerialApiCapabilitiesResponseOptions @messageTypes(MessageType.Response, FunctionType.GetSerialApiCapabilities) export class GetSerialApiCapabilitiesResponse extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetSerialApiCapabilitiesResponseOptions, + options: GetSerialApiCapabilitiesResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - if (gotDeserializationOptions(options)) { - // The first 8 bytes are the api version, manufacturer id, product type and product id - this.firmwareVersion = `${this.payload[0]}.${this.payload[1]}`; - this.manufacturerId = this.payload.readUInt16BE(2); - this.productType = this.payload.readUInt16BE(4); - this.productId = this.payload.readUInt16BE(6); - // then a 256bit bitmask for the supported command classes follows - const functionBitMask = this.payload.subarray( - 8, - 8 + NUM_FUNCTION_BYTES, - ); - this.supportedFunctionTypes = parseBitMask(functionBitMask); - } else { - this.firmwareVersion = options.firmwareVersion; - this.manufacturerId = options.manufacturerId; - this.productType = options.productType; - this.productId = options.productId; - this.supportedFunctionTypes = options.supportedFunctionTypes; - } + this.firmwareVersion = options.firmwareVersion; + this.manufacturerId = options.manufacturerId; + this.productType = options.productType; + this.productId = options.productId; + this.supportedFunctionTypes = options.supportedFunctionTypes; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetSerialApiCapabilitiesResponse { + // The first 8 bytes are the api version, manufacturer id, product type and product id + const firmwareVersion = `${raw.payload[0]}.${raw.payload[1]}`; + const manufacturerId = raw.payload.readUInt16BE(2); + const productType = raw.payload.readUInt16BE(4); + const productId = raw.payload.readUInt16BE(6); + + // then a 256bit bitmask for the supported command classes follows + const functionBitMask = raw.payload.subarray( + 8, + 8 + NUM_FUNCTION_BYTES, + ); + const supportedFunctionTypes: FunctionType[] = parseBitMask( + functionBitMask, + ); + + return new this({ + firmwareVersion, + manufacturerId, + productType, + productId, + supportedFunctionTypes, + }); } public firmwareVersion: string; @@ -67,7 +76,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 +95,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/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts similarity index 62% rename from packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts rename to packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts index c6982f85dedc..da55f570d9f7 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/GetSerialApiInitDataMessages.ts +++ b/packages/serial/src/serialapi/capability/GetSerialApiInitDataMessages.ts @@ -4,6 +4,7 @@ import { NUM_NODEMASK_BYTES, NodeType, type SerialApiInitData, + type ZWaveApiVersion, encodeBitMask, parseNodeBitMask, } from "@zwave-js/core"; @@ -12,98 +13,111 @@ import { getChipTypeAndVersion, getZWaveChipType, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; -import type { ZWaveApiVersion } from "../_Types"; @messageTypes(MessageType.Request, FunctionType.GetSerialApiInitData) @expectedResponse(FunctionType.GetSerialApiInitData) @priority(MessagePriority.Controller) export class GetSerialApiInitDataRequest extends Message {} +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface GetSerialApiInitDataResponseOptions - extends MessageBaseOptions, SerialApiInitData + extends SerialApiInitData {} @messageTypes(MessageType.Response, FunctionType.GetSerialApiInitData) export class GetSerialApiInitDataResponse extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetSerialApiInitDataResponseOptions, + options: GetSerialApiInitDataResponseOptions & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - const apiVersion = this.payload[0]; - if (apiVersion < 10) { - this.zwaveApiVersion = { - kind: "legacy", - version: apiVersion, - }; - } else { - // this module uses the officially specified Host API - this.zwaveApiVersion = { - kind: "official", - version: apiVersion - 9, - }; - } + super(options); + + this.zwaveApiVersion = options.zwaveApiVersion; + this.isPrimary = options.isPrimary; + this.nodeType = options.nodeType; + this.supportsTimers = options.supportsTimers; + this.isSIS = options.isSIS; + this.nodeIds = options.nodeIds; + this.zwaveChipType = options.zwaveChipType; + } - const capabilities = this.payload[1]; - // The new "official" Host API specs incorrectly switched the meaning of some flags - // Apparently this was never intended, and the firmware correctly uses the "old" encoding. - // https://community.silabs.com/s/question/0D58Y00009qjEghSAE/bug-in-firmware-7191-get-init-data-response-does-not-match-host-api-specification?language=en_US - this.nodeType = capabilities & 0b0001 - ? NodeType["End Node"] - : NodeType.Controller; - this.supportsTimers = !!(capabilities & 0b0010); - this.isPrimary = !(capabilities & 0b0100); - this.isSIS = !!(capabilities & 0b1000); - - let offset = 2; - this.nodeIds = []; - if (this.payload.length > offset) { - const nodeListLength = this.payload[offset]; - // Controller Nodes MUST set this field to 29 - if ( - nodeListLength === NUM_NODEMASK_BYTES - && this.payload.length >= offset + 1 + nodeListLength - ) { - const nodeBitMask = this.payload.subarray( - offset + 1, - offset + 1 + nodeListLength, - ); - this.nodeIds = parseNodeBitMask(nodeBitMask); - } - offset += 1 + nodeListLength; - } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetSerialApiInitDataResponse { + const apiVersion = raw.payload[0]; + let zwaveApiVersion: ZWaveApiVersion; + + if (apiVersion < 10) { + zwaveApiVersion = { + kind: "legacy", + version: apiVersion, + }; + } else { + // this module uses the officially specified Host API + zwaveApiVersion = { + kind: "official", + version: apiVersion - 9, + }; + } - // these might not be present: - const chipType = this.payload[offset]; - const chipVersion = this.payload[offset + 1]; - if (chipType != undefined && chipVersion != undefined) { - this.zwaveChipType = getZWaveChipType(chipType, chipVersion); + const capabilities = raw.payload[1]; + // The new "official" Host API specs incorrectly switched the meaning of some flags + // Apparently this was never intended, and the firmware correctly uses the "old" encoding. + // https://community.silabs.com/s/question/0D58Y00009qjEghSAE/bug-in-firmware-7191-get-init-data-response-does-not-match-host-api-specification?language=en_US + const nodeType: NodeType = capabilities & 0b0001 + ? NodeType["End Node"] + : NodeType.Controller; + const supportsTimers = !!(capabilities & 0b0010); + const isPrimary = !(capabilities & 0b0100); + const isSIS = !!(capabilities & 0b1000); + let offset = 2; + let nodeIds: number[] = []; + if (raw.payload.length > offset) { + const nodeListLength = raw.payload[offset]; + // Controller Nodes MUST set this field to 29 + if ( + nodeListLength === NUM_NODEMASK_BYTES + && raw.payload.length >= offset + 1 + nodeListLength + ) { + const nodeBitMask = raw.payload.subarray( + offset + 1, + offset + 1 + nodeListLength, + ); + nodeIds = parseNodeBitMask(nodeBitMask); } - } else { - this.zwaveApiVersion = options.zwaveApiVersion; - this.isPrimary = options.isPrimary; - this.nodeType = options.nodeType; - this.supportsTimers = options.supportsTimers; - this.isSIS = options.isSIS; - this.nodeIds = options.nodeIds; - this.zwaveChipType = options.zwaveChipType; + offset += 1 + nodeListLength; } + + // these might not be present: + const chipType = raw.payload[offset]; + const chipVersion = raw.payload[offset + 1]; + let zwaveChipType: string | UnknownZWaveChipType | undefined; + + if (chipType != undefined && chipVersion != undefined) { + zwaveChipType = getZWaveChipType(chipType, chipVersion); + } + + return new this({ + zwaveApiVersion, + nodeType, + supportsTimers, + isPrimary, + isSIS, + nodeIds, + zwaveChipType, + }); } public zwaveApiVersion: ZWaveApiVersion; @@ -117,7 +131,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 +166,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/serial/src/serialapi/capability/HardResetRequest.ts b/packages/serial/src/serialapi/capability/HardResetRequest.ts new file mode 100644 index 000000000000..f83825ffb9c7 --- /dev/null +++ b/packages/serial/src/serialapi/capability/HardResetRequest.ts @@ -0,0 +1,69 @@ +import type { MessageOrCCLogEntry } from "@zwave-js/core"; +import { MessagePriority } from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedCallback, + messageTypes, + priority, +} from "@zwave-js/serial"; + +@messageTypes(MessageType.Request, FunctionType.HardReset) +@priority(MessagePriority.Controller) +export class HardResetRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): HardResetRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return HardResetRequest.from(raw, ctx); + } else { + return HardResetCallback.from(raw, ctx); + } + } +} + +@expectedCallback(FunctionType.HardReset) +export class HardResetRequest extends HardResetRequestBase { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + this.payload = Buffer.from([this.callbackId]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + }, + }; + } +} + +export class HardResetCallback extends HardResetRequestBase { + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): HardResetCallback { + const callbackId = raw.payload[0]; + + return new this({ + callbackId, + }); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + }, + }; + } +} diff --git a/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts b/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts new file mode 100644 index 000000000000..cedd32833176 --- /dev/null +++ b/packages/serial/src/serialapi/capability/LongRangeChannelMessages.ts @@ -0,0 +1,167 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + ZWaveError, + ZWaveErrorCodes, +} from "@zwave-js/core"; +import { LongRangeChannel } from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + type SuccessIndicator, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.GetLongRangeChannel) +@expectedResponse(FunctionType.GetLongRangeChannel) +@priority(MessagePriority.Controller) +export class GetLongRangeChannelRequest extends Message {} + +export interface GetLongRangeChannelResponseOptions { + channel: + | LongRangeChannel.Unsupported + | LongRangeChannel.A + | LongRangeChannel.B; + supportsAutoChannelSelection: boolean; + autoChannelSelectionActive: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.GetLongRangeChannel) +@priority(MessagePriority.Controller) +export class GetLongRangeChannelResponse extends Message { + public constructor( + options: GetLongRangeChannelResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.channel = options.channel; + this.supportsAutoChannelSelection = + options.supportsAutoChannelSelection; + this.autoChannelSelectionActive = options.autoChannelSelectionActive; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetLongRangeChannelResponse { + const channel: GetLongRangeChannelResponseOptions["channel"] = + raw.payload[0]; + let supportsAutoChannelSelection: boolean; + let autoChannelSelectionActive: boolean; + if (raw.payload.length >= 2) { + supportsAutoChannelSelection = !!(raw.payload[1] & 0b0001_0000); + autoChannelSelectionActive = !!(raw.payload[1] & 0b0010_0000); + } else { + supportsAutoChannelSelection = false; + autoChannelSelectionActive = false; + } + + return new this({ + channel, + supportsAutoChannelSelection, + autoChannelSelectionActive, + }); + } + + public readonly channel: + | LongRangeChannel.A + | LongRangeChannel.B + | LongRangeChannel.Unsupported; + public readonly supportsAutoChannelSelection: boolean; + public readonly autoChannelSelectionActive: boolean; +} + +export interface SetLongRangeChannelRequestOptions { + channel: LongRangeChannel; +} + +@messageTypes(MessageType.Request, FunctionType.SetLongRangeChannel) +@priority(MessagePriority.Controller) +@expectedResponse(FunctionType.SetLongRangeChannel) +export class SetLongRangeChannelRequest extends Message { + public constructor( + options: SetLongRangeChannelRequestOptions & MessageBaseOptions, + ) { + super(options); + this.channel = options.channel; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLongRangeChannelRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SetLongRangeChannelRequest({}); + } + + public channel: LongRangeChannel; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.channel]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + channel: getEnumMemberName(LongRangeChannel, this.channel), + }, + }; + } +} + +export interface SetLongRangeChannelResponseOptions { + success: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SetLongRangeChannel) +export class SetLongRangeChannelResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: SetLongRangeChannelResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLongRangeChannelResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); + } + + isOK(): boolean { + return this.success; + } + + public readonly success: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { success: this.success }, + }; + } +} diff --git a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts similarity index 83% rename from packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts rename to packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts index 71ebbe27a170..87f181df0d87 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.test.ts +++ b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.test.ts @@ -1,17 +1,17 @@ -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.parse( + data, + { sdkVersion: "7.19.0" } as any, + ); t.true(msg instanceof SerialAPISetup_GetSupportedCommandsResponse); const supported = (msg as SerialAPISetup_GetSupportedCommandsResponse) .supportedCommands; @@ -28,7 +28,10 @@ test("GetSupportedCommandsResponse with extended bitmask parses correctly (post- "hex", ); - const msg = Message.from(host, { data, sdkVersion: "7.19.1" }); + const msg = Message.parse( + data, + { sdkVersion: "7.19.1" } 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/serial/src/serialapi/capability/SerialAPISetupMessages.ts similarity index 56% rename from packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts rename to packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts index a783fb392a31..9dcc5d212ae5 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SerialAPISetupMessages.ts +++ b/packages/serial/src/serialapi/capability/SerialAPISetupMessages.ts @@ -10,25 +10,24 @@ import { parseBitMask, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; +import { sdkVersionLt } from "@zwave-js/core"; import type { - DeserializingMessageConstructor, + MessageConstructor, + MessageEncodingContext, + MessageParsingContext, + MessageRaw, SuccessIndicator, } from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; import { getEnumMemberName } from "@zwave-js/shared"; -import { sdkVersionLt } from "../../controller/utils"; export enum SerialAPISetupCommand { Unsupported = 0x00, @@ -53,12 +52,12 @@ export enum SerialAPISetupCommand { // We need to define the decorators for Requests and Responses separately const { decorator: subCommandRequest, - // lookupConstructor: getSubCommandRequestConstructor, + lookupConstructor: getSubCommandRequestConstructor, lookupValue: getSubCommandForRequest, } = createSimpleReflectionDecorator< SerialAPISetupRequest, [command: SerialAPISetupCommand], - DeserializingMessageConstructor + MessageConstructor >({ name: "subCommandRequest", }); @@ -66,10 +65,11 @@ const { const { decorator: subCommandResponse, lookupConstructor: getSubCommandResponseConstructor, + lookupValue: getSubCommandForResponse, } = createSimpleReflectionDecorator< SerialAPISetupResponse, [command: SerialAPISetupCommand], - DeserializingMessageConstructor + MessageConstructor >({ name: "subCommandResponse", }); @@ -82,31 +82,54 @@ function testResponseForSerialAPISetupRequest( return (sent as SerialAPISetupRequest).command === received.command; } +export interface SerialAPISetupRequestOptions { + command?: SerialAPISetupCommand; +} + @messageTypes(MessageType.Request, FunctionType.SerialAPISetup) @priority(MessagePriority.Controller) @expectedResponse(testResponseForSerialAPISetupRequest) export class SerialAPISetupRequest extends Message { - public constructor(host: ZWaveHost, options: MessageOptions = {}) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.command = getSubCommandForRequest(this)!; + public constructor( + options: SerialAPISetupRequestOptions & MessageBaseOptions = {}, + ) { + super(options); + this.command = options.command ?? getSubCommandForRequest(this)!; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SerialAPISetupRequest { + const command: SerialAPISetupCommand = raw.payload[0]; + const payload = raw.payload.subarray(1); + + const CommandConstructor = getSubCommandRequestConstructor( + command, + ); + if (CommandConstructor) { + return CommandConstructor.from( + raw.withPayload(payload), + ctx, + ) as SerialAPISetupRequest; } + + const ret = new SerialAPISetupRequest({ + command, + }); + ret.payload = payload; + return ret; } 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 { @@ -123,23 +146,41 @@ export class SerialAPISetupRequest extends Message { } } +export interface SerialAPISetupResponseOptions { + command?: SerialAPISetupCommand; +} + @messageTypes(MessageType.Response, FunctionType.SerialAPISetup) export class SerialAPISetupResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SerialAPISetupResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.command = this.payload[0]; + super(options); + this.command = options.command ?? getSubCommandForResponse(this)!; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SerialAPISetupResponse { + const command: SerialAPISetupCommand = raw.payload[0]; + const payload = raw.payload.subarray(1); const CommandConstructor = getSubCommandResponseConstructor( - this.command, + command, ); - if (CommandConstructor && (new.target as any) !== CommandConstructor) { - return new CommandConstructor(host, options); + if (CommandConstructor) { + return CommandConstructor.from( + raw.withPayload(payload), + ctx, + ) as SerialAPISetupResponse; } - this.payload = this.payload.subarray(1); + const ret = new SerialAPISetupResponse({ + command, + }); + ret.payload = payload; + return ret; } public command: SerialAPISetupCommand; @@ -158,17 +199,33 @@ export class SerialAPISetupResponse extends Message { } } +export interface SerialAPISetup_CommandUnsupportedResponseOptions { + command: SerialAPISetupCommand; +} + @subCommandResponse(0x00) export class SerialAPISetup_CommandUnsupportedResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_CommandUnsupportedResponseOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); + this.command = options.command; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_CommandUnsupportedResponse { // The payload contains which command is unsupported - this.command = this.payload[0]; + const command: any = raw.payload[0]; + + return new this({ + command, + }); } public toLogEntry(): MessageOrCCLogEntry { @@ -189,11 +246,10 @@ export class SerialAPISetup_CommandUnsupportedResponse @subCommandRequest(SerialAPISetupCommand.GetSupportedCommands) export class SerialAPISetup_GetSupportedCommandsRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetSupportedCommands; - } +{} + +export interface SerialAPISetup_GetSupportedCommandsResponseOptions { + supportedCommands: SerialAPISetupCommand[]; } @subCommandResponse(SerialAPISetupCommand.GetSupportedCommands) @@ -201,27 +257,35 @@ export class SerialAPISetup_GetSupportedCommandsResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetSupportedCommandsResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 1); + super(options); + this.supportedCommands = options.supportedCommands; + } - if (this.payload.length > 1) { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SerialAPISetup_GetSupportedCommandsResponse { + validatePayload(raw.payload.length >= 1); + let supportedCommands: SerialAPISetupCommand[]; + if (raw.payload.length > 1) { // This module supports the extended bitmask to report the supported serial API setup commands // Parse it as a bitmask - this.supportedCommands = parseBitMask( - this.payload.subarray(1), + supportedCommands = parseBitMask( + raw.payload.subarray(1), // According to the Host API specification, the first bit (bit 0) should be GetSupportedCommands // However, in Z-Wave SDK < 7.19.1, the entire bitmask is shifted by 1 bit and // GetSupportedCommands is encoded in the second bit (bit 1) - sdkVersionLt(options.sdkVersion, "7.19.1") + sdkVersionLt(ctx.sdkVersion, "7.19.1") ? SerialAPISetupCommand.Unsupported : SerialAPISetupCommand.GetSupportedCommands, ); } else { // This module only uses the single byte power-of-2 bitmask. Decode it manually - this.supportedCommands = []; + supportedCommands = []; for ( const cmd of [ SerialAPISetupCommand.GetSupportedCommands, @@ -234,20 +298,25 @@ export class SerialAPISetup_GetSupportedCommandsResponse SerialAPISetupCommand.SetNodeIDType, ] as const ) { - if (!!(this.payload[0] & cmd)) this.supportedCommands.push(cmd); + if (!!(raw.payload[0] & cmd)) supportedCommands.push(cmd); } } + // Apparently GetSupportedCommands is not always included in the bitmask, although we // just received a response to the command if ( - !this.supportedCommands.includes( + !supportedCommands.includes( SerialAPISetupCommand.GetSupportedCommands, ) ) { - this.supportedCommands.unshift( + supportedCommands.unshift( SerialAPISetupCommand.GetSupportedCommands, ); } + + return new this({ + supportedCommands, + }); } public readonly supportedCommands: SerialAPISetupCommand[]; @@ -255,9 +324,7 @@ export class SerialAPISetup_GetSupportedCommandsResponse // ============================================================================= -export interface SerialAPISetup_SetTXStatusReportOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_SetTXStatusReportOptions { enabled: boolean; } @@ -266,30 +333,29 @@ export class SerialAPISetup_SetTXStatusReportRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_SetTXStatusReportOptions, + options: SerialAPISetup_SetTXStatusReportOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetTxStatusReport; + super(options); + this.enabled = options.enabled; + } - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.enabled = options.enabled; - } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetTXStatusReportRequest { + const enabled = raw.payload[0] === 0xff; + + return new this({ + enabled, + }); } 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 { @@ -301,17 +367,33 @@ export class SerialAPISetup_SetTXStatusReportRequest } } +export interface SerialAPISetup_SetTXStatusReportResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetTxStatusReport) export class SerialAPISetup_SetTXStatusReportResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_SetTXStatusReportResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetTXStatusReportResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -331,39 +413,37 @@ export class SerialAPISetup_SetTXStatusReportResponse // ============================================================================= -export interface SerialAPISetup_SetNodeIDTypeOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_SetNodeIDTypeOptions { nodeIdType: NodeIDType; } @subCommandRequest(SerialAPISetupCommand.SetNodeIDType) export class SerialAPISetup_SetNodeIDTypeRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_SetNodeIDTypeOptions, + options: SerialAPISetup_SetNodeIDTypeOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetNodeIDType; + super(options); + this.nodeIdType = options.nodeIdType; + } - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.nodeIdType = options.nodeIdType; - } + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetNodeIDTypeRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SerialAPISetup_SetNodeIDTypeRequest({}); } 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 { @@ -377,16 +457,32 @@ export class SerialAPISetup_SetNodeIDTypeRequest extends SerialAPISetupRequest { } } +export interface SerialAPISetup_SetNodeIDTypeResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetNodeIDType) export class SerialAPISetup_SetNodeIDTypeResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_SetNodeIDTypeResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetNodeIDTypeResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -407,21 +503,30 @@ export class SerialAPISetup_SetNodeIDTypeResponse extends SerialAPISetupResponse // ============================================================================= @subCommandRequest(SerialAPISetupCommand.GetRFRegion) -export class SerialAPISetup_GetRFRegionRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetRFRegion; - } +export class SerialAPISetup_GetRFRegionRequest extends SerialAPISetupRequest {} + +export interface SerialAPISetup_GetRFRegionResponseOptions { + region: RFRegion; } @subCommandResponse(SerialAPISetupCommand.GetRFRegion) export class SerialAPISetup_GetRFRegionResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SerialAPISetup_GetRFRegionResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.region = this.payload[0]; + super(options); + this.region = options.region; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetRFRegionResponse { + const region: RFRegion = raw.payload[0]; + + return new this({ + region, + }); } public readonly region: RFRegion; @@ -437,36 +542,35 @@ export class SerialAPISetup_GetRFRegionResponse extends SerialAPISetupResponse { // ============================================================================= -export interface SerialAPISetup_SetRFRegionOptions extends MessageBaseOptions { +export interface SerialAPISetup_SetRFRegionOptions { region: RFRegion; } @subCommandRequest(SerialAPISetupCommand.SetRFRegion) export class SerialAPISetup_SetRFRegionRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_SetRFRegionOptions, + options: SerialAPISetup_SetRFRegionOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetRFRegion; + super(options); + this.region = options.region; + } - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.region = options.region; - } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetRFRegionRequest { + const region: RFRegion = raw.payload[0]; + + return new this({ + region, + }); } 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 { @@ -478,16 +582,30 @@ export class SerialAPISetup_SetRFRegionRequest extends SerialAPISetupRequest { } } +export interface SerialAPISetup_SetRFRegionResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetRFRegion) export class SerialAPISetup_SetRFRegionResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SerialAPISetup_SetRFRegionResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetRFRegionResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -508,11 +626,13 @@ export class SerialAPISetup_SetRFRegionResponse extends SerialAPISetupResponse // ============================================================================= @subCommandRequest(SerialAPISetupCommand.GetPowerlevel) -export class SerialAPISetup_GetPowerlevelRequest extends SerialAPISetupRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetPowerlevel; - } +export class SerialAPISetup_GetPowerlevelRequest + extends SerialAPISetupRequest +{} + +export interface SerialAPISetup_GetPowerlevelResponseOptions { + powerlevel: number; + measured0dBm: number; } @subCommandResponse(SerialAPISetupCommand.GetPowerlevel) @@ -520,14 +640,28 @@ export class SerialAPISetup_GetPowerlevelResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetPowerlevelResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 2); + super(options); + this.powerlevel = options.powerlevel; + this.measured0dBm = options.measured0dBm; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetPowerlevelResponse { + validatePayload(raw.payload.length >= 2); // The values are in 0.1 dBm, signed - this.powerlevel = this.payload.readInt8(0) / 10; - this.measured0dBm = this.payload.readInt8(1) / 10; + const powerlevel = raw.payload.readInt8(0) / 10; + const measured0dBm = raw.payload.readInt8(1) / 10; + + return new this({ + powerlevel, + measured0dBm, + }); } /** The configured normal powerlevel in dBm */ @@ -550,9 +684,7 @@ export class SerialAPISetup_GetPowerlevelResponse // ============================================================================= -export interface SerialAPISetup_SetPowerlevelOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_SetPowerlevelOptions { powerlevel: number; measured0dBm: number; } @@ -560,47 +692,48 @@ export interface SerialAPISetup_SetPowerlevelOptions @subCommandRequest(SerialAPISetupCommand.SetPowerlevel) export class SerialAPISetup_SetPowerlevelRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_SetPowerlevelOptions, + options: SerialAPISetup_SetPowerlevelOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetPowerlevel; + super(options); - if (gotDeserializationOptions(options)) { + if (options.powerlevel < -12.8 || options.powerlevel > 12.7) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The normal powerlevel must be between -12.8 and +12.7 dBm`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.measured0dBm < -12.8 || options.measured0dBm > 12.7) { + throw new ZWaveError( + `The measured output power at 0 dBm must be between -12.8 and +12.7 dBm`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.powerlevel < -12.8 || options.powerlevel > 12.7) { - throw new ZWaveError( - `The normal powerlevel must be between -12.8 and +12.7 dBm`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.measured0dBm < -12.8 || options.measured0dBm > 12.7) { - throw new ZWaveError( - `The measured output power at 0 dBm must be between -12.8 and +12.7 dBm`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.powerlevel = options.powerlevel; - this.measured0dBm = options.measured0dBm; } + this.powerlevel = options.powerlevel; + this.measured0dBm = options.measured0dBm; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetPowerlevelRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SerialAPISetup_SetPowerlevelRequest({}); } 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 { @@ -616,16 +749,32 @@ export class SerialAPISetup_SetPowerlevelRequest extends SerialAPISetupRequest { } } +export interface SerialAPISetup_SetPowerlevelResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetPowerlevel) export class SerialAPISetup_SetPowerlevelResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_SetPowerlevelResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetPowerlevelResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -648,11 +797,11 @@ export class SerialAPISetup_SetPowerlevelResponse extends SerialAPISetupResponse @subCommandRequest(SerialAPISetupCommand.GetPowerlevel16Bit) export class SerialAPISetup_GetPowerlevel16BitRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetPowerlevel16Bit; - } +{} + +export interface SerialAPISetup_GetPowerlevel16BitResponseOptions { + powerlevel: number; + measured0dBm: number; } @subCommandResponse(SerialAPISetupCommand.GetPowerlevel16Bit) @@ -660,14 +809,28 @@ export class SerialAPISetup_GetPowerlevel16BitResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetPowerlevel16BitResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 4); + super(options); + this.powerlevel = options.powerlevel; + this.measured0dBm = options.measured0dBm; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetPowerlevel16BitResponse { + validatePayload(raw.payload.length >= 4); // The values are in 0.1 dBm, signed - this.powerlevel = this.payload.readInt16BE(0) / 10; - this.measured0dBm = this.payload.readInt16BE(2) / 10; + const powerlevel = raw.payload.readInt16BE(0) / 10; + const measured0dBm = raw.payload.readInt16BE(2) / 10; + + return new this({ + powerlevel, + measured0dBm, + }); } /** The configured normal powerlevel in dBm */ @@ -690,9 +853,7 @@ export class SerialAPISetup_GetPowerlevel16BitResponse // ============================================================================= -export interface SerialAPISetup_SetPowerlevel16BitOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_SetPowerlevel16BitOptions { powerlevel: number; measured0dBm: number; } @@ -702,47 +863,48 @@ export class SerialAPISetup_SetPowerlevel16BitRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_SetPowerlevel16BitOptions, + options: SerialAPISetup_SetPowerlevel16BitOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetPowerlevel16Bit; + super(options); - if (gotDeserializationOptions(options)) { + if (options.powerlevel < -10 || options.powerlevel > 20) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The normal powerlevel must be between -10.0 and +20.0 dBm`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.measured0dBm < -10 || options.measured0dBm > 10) { + throw new ZWaveError( + `The measured output power at 0 dBm must be between -10.0 and +10.0 dBm`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.powerlevel < -10 || options.powerlevel > 20) { - throw new ZWaveError( - `The normal powerlevel must be between -10.0 and +20.0 dBm`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.measured0dBm < -10 || options.measured0dBm > 10) { - throw new ZWaveError( - `The measured output power at 0 dBm must be between -10.0 and +10.0 dBm`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.powerlevel = options.powerlevel; - this.measured0dBm = options.measured0dBm; } + this.powerlevel = options.powerlevel; + this.measured0dBm = options.measured0dBm; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetPowerlevel16BitRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new 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 { @@ -758,17 +920,33 @@ export class SerialAPISetup_SetPowerlevel16BitRequest } } +export interface SerialAPISetup_SetPowerlevel16BitResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetPowerlevel16Bit) export class SerialAPISetup_SetPowerlevel16BitResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_SetPowerlevel16BitResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetPowerlevel16BitResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -791,11 +969,10 @@ export class SerialAPISetup_SetPowerlevel16BitResponse @subCommandRequest(SerialAPISetupCommand.GetLongRangeMaximumTxPower) export class SerialAPISetup_GetLongRangeMaximumTxPowerRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetLongRangeMaximumTxPower; - } +{} + +export interface SerialAPISetup_GetLongRangeMaximumTxPowerResponseOptions { + limit: number; } @subCommandResponse(SerialAPISetupCommand.GetLongRangeMaximumTxPower) @@ -803,13 +980,25 @@ export class SerialAPISetup_GetLongRangeMaximumTxPowerResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetLongRangeMaximumTxPowerResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 2); + super(options); + this.limit = options.limit; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetLongRangeMaximumTxPowerResponse { + validatePayload(raw.payload.length >= 2); // The values are in 0.1 dBm, signed - this.limit = this.payload.readInt16BE(0) / 10; + const limit = raw.payload.readInt16BE(0) / 10; + + return new this({ + limit, + }); } /** The maximum LR TX power in dBm */ @@ -829,9 +1018,7 @@ export class SerialAPISetup_GetLongRangeMaximumTxPowerResponse // ============================================================================= -export interface SerialAPISetup_SetLongRangeMaximumTxPowerOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_SetLongRangeMaximumTxPowerOptions { limit: number; } @@ -840,40 +1027,43 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | SerialAPISetup_SetLongRangeMaximumTxPowerOptions, + & SerialAPISetup_SetLongRangeMaximumTxPowerOptions + & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.SetLongRangeMaximumTxPower; + super(options); - if (gotDeserializationOptions(options)) { + if (options.limit < -10 || options.limit > 20) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The maximum LR TX power must be between -10.0 and +20.0 dBm`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.limit < -10 || options.limit > 20) { - throw new ZWaveError( - `The maximum LR TX power must be between -10.0 and +20.0 dBm`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.limit = options.limit; } + + this.limit = options.limit; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetLongRangeMaximumTxPowerRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new 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 { @@ -888,17 +1078,33 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerRequest } } +export interface SerialAPISetup_SetLongRangeMaximumTxPowerResponseOptions { + success: boolean; +} + @subCommandResponse(SerialAPISetupCommand.SetLongRangeMaximumTxPower) export class SerialAPISetup_SetLongRangeMaximumTxPowerResponse extends SerialAPISetupResponse implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_SetLongRangeMaximumTxPowerResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_SetLongRangeMaximumTxPowerResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { @@ -921,11 +1127,10 @@ export class SerialAPISetup_SetLongRangeMaximumTxPowerResponse @subCommandRequest(SerialAPISetupCommand.GetMaximumPayloadSize) export class SerialAPISetup_GetMaximumPayloadSizeRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetMaximumPayloadSize; - } +{} + +export interface SerialAPISetup_GetMaximumPayloadSizeResponseOptions { + maxPayloadSize: number; } @subCommandResponse(SerialAPISetupCommand.GetMaximumPayloadSize) @@ -933,11 +1138,23 @@ export class SerialAPISetup_GetMaximumPayloadSizeResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetMaximumPayloadSizeResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.maxPayloadSize = this.payload[0]; + super(options); + this.maxPayloadSize = options.maxPayloadSize; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetMaximumPayloadSizeResponse { + const maxPayloadSize = raw.payload[0]; + + return new this({ + maxPayloadSize, + }); } public readonly maxPayloadSize: number; @@ -956,11 +1173,10 @@ export class SerialAPISetup_GetMaximumPayloadSizeResponse @subCommandRequest(SerialAPISetupCommand.GetLongRangeMaximumPayloadSize) export class SerialAPISetup_GetLongRangeMaximumPayloadSizeRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetLongRangeMaximumPayloadSize; - } +{} + +export interface SerialAPISetup_GetLongRangeMaximumPayloadSizeResponseOptions { + maxPayloadSize: number; } @subCommandResponse(SerialAPISetupCommand.GetLongRangeMaximumPayloadSize) @@ -968,11 +1184,23 @@ export class SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetLongRangeMaximumPayloadSizeResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.maxPayloadSize = this.payload[0]; + super(options); + this.maxPayloadSize = options.maxPayloadSize; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse { + const maxPayloadSize = raw.payload[0]; + + return new this({ + maxPayloadSize, + }); } public readonly maxPayloadSize: number; @@ -991,11 +1219,10 @@ export class SerialAPISetup_GetLongRangeMaximumPayloadSizeResponse @subCommandRequest(SerialAPISetupCommand.GetSupportedRegions) export class SerialAPISetup_GetSupportedRegionsRequest extends SerialAPISetupRequest -{ - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); - this.command = SerialAPISetupCommand.GetSupportedRegions; - } +{} + +export interface SerialAPISetup_GetSupportedRegionsResponseOptions { + supportedRegions: RFRegion[]; } @subCommandResponse(SerialAPISetupCommand.GetSupportedRegions) @@ -1003,16 +1230,28 @@ export class SerialAPISetup_GetSupportedRegionsResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetSupportedRegionsResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 1); + super(options); + this.supportedRegions = options.supportedRegions; + } - const numRegions = this.payload[0]; - validatePayload(numRegions > 0, this.payload.length >= 1 + numRegions); + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetSupportedRegionsResponse { + validatePayload(raw.payload.length >= 1); + const numRegions = raw.payload[0]; + validatePayload(numRegions > 0, raw.payload.length >= 1 + numRegions); + const supportedRegions: RFRegion[] = [ + ...raw.payload.subarray(1, 1 + numRegions), + ]; - this.supportedRegions = [...this.payload.subarray(1, 1 + numRegions)]; + return new this({ + supportedRegions, + }); } public readonly supportedRegions: RFRegion[]; @@ -1020,38 +1259,35 @@ export class SerialAPISetup_GetSupportedRegionsResponse // ============================================================================= -export interface SerialAPISetup_GetRegionInfoOptions - extends MessageBaseOptions -{ +export interface SerialAPISetup_GetRegionInfoOptions { region: RFRegion; } @subCommandRequest(SerialAPISetupCommand.GetRegionInfo) export class SerialAPISetup_GetRegionInfoRequest extends SerialAPISetupRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SerialAPISetup_GetRegionInfoOptions, + options: SerialAPISetup_GetRegionInfoOptions & MessageBaseOptions, ) { - super(host, options); - this.command = SerialAPISetupCommand.GetRegionInfo; + super(options); + this.region = options.region; + } - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.region = options.region; - } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetRegionInfoRequest { + const region: RFRegion = raw.payload[0]; + + return new this({ + region, + }); } 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 { @@ -1066,24 +1302,50 @@ export class SerialAPISetup_GetRegionInfoRequest extends SerialAPISetupRequest { } } +export interface SerialAPISetup_GetRegionInfoResponseOptions { + region: RFRegion; + supportsZWave: boolean; + supportsLongRange: boolean; + includesRegion?: RFRegion; +} + @subCommandResponse(SerialAPISetupCommand.GetRegionInfo) export class SerialAPISetup_GetRegionInfoResponse extends SerialAPISetupResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & SerialAPISetup_GetRegionInfoResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.region = this.payload[0]; - this.supportsZWave = !!(this.payload[1] & 0b1); - this.supportsLongRange = !!(this.payload[1] & 0b10); - if (this.payload.length > 2) { - this.includesRegion = this.payload[2]; - if (this.includesRegion === RFRegion.Unknown) { - this.includesRegion = undefined; + super(options); + this.region = options.region; + this.supportsZWave = options.supportsZWave; + this.supportsLongRange = options.supportsLongRange; + this.includesRegion = options.includesRegion; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SerialAPISetup_GetRegionInfoResponse { + const region: RFRegion = raw.payload[0]; + const supportsZWave = !!(raw.payload[1] & 0b1); + const supportsLongRange = !!(raw.payload[1] & 0b10); + let includesRegion: RFRegion | undefined; + if (raw.payload.length > 2) { + includesRegion = raw.payload[2]; + if (includesRegion === RFRegion.Unknown) { + includesRegion = undefined; } } + + return new this({ + region, + supportsZWave, + supportsLongRange, + includesRegion, + }); } public readonly region: RFRegion; diff --git a/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts b/packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts similarity index 93% rename from packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts rename to packages/serial/src/serialapi/capability/SetApplicationNodeInformationRequest.ts index 1168ea195f7c..2fc8f9c42bba 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SetApplicationNodeInformationRequest.ts +++ b/packages/serial/src/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/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts similarity index 54% rename from packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts rename to packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts index a0033d3e0faf..54e14ce968ac 100644 --- a/packages/zwave-js/src/lib/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts +++ b/packages/serial/src/serialapi/capability/SetLongRangeShadowNodeIDsRequest.ts @@ -1,19 +1,17 @@ import { MessagePriority, encodeBitMask, parseBitMask } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; -export interface LongRangeShadowNodeIDsRequestOptions - extends MessageBaseOptions -{ +export interface LongRangeShadowNodeIDsRequestOptions { shadowNodeIds: number[]; } @@ -24,27 +22,31 @@ const NUM_LONG_RANGE_SHADOW_NODE_IDS = 4; @priority(MessagePriority.Controller) export class SetLongRangeShadowNodeIDsRequest extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | LongRangeShadowNodeIDsRequestOptions, + options: LongRangeShadowNodeIDsRequestOptions & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.shadowNodeIds = parseBitMask( - this.payload.subarray(0, 1), - LONG_RANGE_SHADOW_NODE_IDS_START, - NUM_LONG_RANGE_SHADOW_NODE_IDS, - ); - } else { - this.shadowNodeIds = options.shadowNodeIds; - } + super(options); + + this.shadowNodeIds = options.shadowNodeIds; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLongRangeShadowNodeIDsRequest { + const shadowNodeIds = parseBitMask( + raw.payload.subarray(0, 1), + LONG_RANGE_SHADOW_NODE_IDS_START, + NUM_LONG_RANGE_SHADOW_NODE_IDS, + ); + + return new this({ + shadowNodeIds, + }); } public shadowNodeIds: number[]; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { this.payload = Buffer.allocUnsafe(1); this.payload = encodeBitMask( this.shadowNodeIds, @@ -54,6 +56,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/serial/src/serialapi/memory/GetControllerIdMessages.ts similarity index 57% rename from packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts rename to packages/serial/src/serialapi/memory/GetControllerIdMessages.ts index ea89fddfb3ce..a6a6968bfc17 100644 --- a/packages/zwave-js/src/lib/serialapi/memory/GetControllerIdMessages.ts +++ b/packages/serial/src/serialapi/memory/GetControllerIdMessages.ts @@ -1,14 +1,14 @@ 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, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -19,7 +19,7 @@ import { num2hex } from "@zwave-js/shared"; @priority(MessagePriority.Controller) export class GetControllerIdRequest extends Message {} -export interface GetControllerIdResponseOptions extends MessageBaseOptions { +export interface GetControllerIdResponseOptions { homeId: number; ownNodeId: number; } @@ -27,32 +27,42 @@ export interface GetControllerIdResponseOptions extends MessageBaseOptions { @messageTypes(MessageType.Response, FunctionType.GetControllerId) export class GetControllerIdResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | GetControllerIdResponseOptions, + options: GetControllerIdResponseOptions & MessageBaseOptions, ) { - super(host, 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); - this.ownNodeId = nodeId; - } else { - this.homeId = options.homeId; - this.ownNodeId = options.ownNodeId; - } + super(options); + this.homeId = options.homeId; + this.ownNodeId = options.ownNodeId; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): GetControllerIdResponse { + // The payload is 4 bytes home id, followed by the controller node id + const homeId = raw.payload.readUInt32BE(0); + const { nodeId: ownNodeId } = parseNodeID( + raw.payload, + ctx.nodeIdType, + 4, + ); + + return new this({ + homeId, + ownNodeId, + }); } 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/serial/src/serialapi/misc/GetBackgroundRSSIMessages.ts similarity index 58% rename from packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts rename to packages/serial/src/serialapi/misc/GetBackgroundRSSIMessages.ts index 3a112049ea14..5d7770fc06de 100644 --- a/packages/zwave-js/src/lib/serialapi/misc/GetBackgroundRSSIMessages.ts +++ b/packages/serial/src/serialapi/misc/GetBackgroundRSSIMessages.ts @@ -5,11 +5,12 @@ import { type RSSI, rssiToString, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, - type MessageDeserializationOptions, + type MessageBaseOptions, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, messageTypes, @@ -22,17 +23,42 @@ import { parseRSSI, tryParseRSSI } from "../transport/SendDataShared"; @expectedResponse(FunctionType.GetBackgroundRSSI) export class GetBackgroundRSSIRequest extends Message {} +export interface GetBackgroundRSSIResponseOptions { + rssiChannel0: number; + rssiChannel1: number; + rssiChannel2?: number; + rssiChannel3?: number; +} + @messageTypes(MessageType.Response, FunctionType.GetBackgroundRSSI) export class GetBackgroundRSSIResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: GetBackgroundRSSIResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.rssiChannel0 = parseRSSI(this.payload, 0); - this.rssiChannel1 = parseRSSI(this.payload, 1); - this.rssiChannel2 = tryParseRSSI(this.payload, 2); - this.rssiChannel3 = tryParseRSSI(this.payload, 3); + super(options); + + // TODO: Check implementation: + this.rssiChannel0 = options.rssiChannel0; + this.rssiChannel1 = options.rssiChannel1; + this.rssiChannel2 = options.rssiChannel2; + this.rssiChannel3 = options.rssiChannel3; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetBackgroundRSSIResponse { + const rssiChannel0 = parseRSSI(raw.payload, 0); + const rssiChannel1 = parseRSSI(raw.payload, 1); + const rssiChannel2 = tryParseRSSI(raw.payload, 2); + const rssiChannel3 = tryParseRSSI(raw.payload, 3); + + return new this({ + rssiChannel0, + rssiChannel1, + rssiChannel2, + rssiChannel3, + }); } public readonly rssiChannel0: RSSI; diff --git a/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts b/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts similarity index 53% rename from packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts rename to packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts index 12451975aa25..a33f499a6f8b 100644 --- a/packages/zwave-js/src/lib/serialapi/misc/SetRFReceiveModeMessages.ts +++ b/packages/serial/src/serialapi/misc/SetRFReceiveModeMessages.ts @@ -4,21 +4,23 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; -export interface SetRFReceiveModeRequestOptions extends MessageBaseOptions { +export interface SetRFReceiveModeRequestOptions { /** Whether the stick should receive (true) or not (false) */ enabled: boolean; } @@ -28,26 +30,30 @@ export interface SetRFReceiveModeRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.SetRFReceiveMode) export class SetRFReceiveModeRequest extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SetRFReceiveModeRequestOptions, + options: SetRFReceiveModeRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.enabled = options.enabled; - } + super(options); + this.enabled = options.enabled; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetRFReceiveModeRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SetRFReceiveModeRequest({}); } 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 { @@ -60,16 +66,32 @@ export class SetRFReceiveModeRequest extends Message { } } +export interface SetRFReceiveModeResponseOptions { + success: boolean; +} + @messageTypes(MessageType.Response, FunctionType.SetRFReceiveMode) export class SetRFReceiveModeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SetRFReceiveModeResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetRFReceiveModeResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } isOK(): boolean { diff --git a/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts b/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts new file mode 100644 index 000000000000..5130568affdb --- /dev/null +++ b/packages/serial/src/serialapi/misc/SetSerialApiTimeoutsMessages.ts @@ -0,0 +1,76 @@ +import { MessagePriority } from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; + +export interface SetSerialApiTimeoutsRequestOptions { + ackTimeout: number; + byteTimeout: number; +} + +@messageTypes(MessageType.Request, FunctionType.SetSerialApiTimeouts) +@expectedResponse(FunctionType.SetSerialApiTimeouts) +@priority(MessagePriority.Controller) +export class SetSerialApiTimeoutsRequest extends Message { + public constructor( + options: SetSerialApiTimeoutsRequestOptions & MessageBaseOptions, + ) { + super(options); + this.ackTimeout = options.ackTimeout; + this.byteTimeout = options.byteTimeout; + } + + public ackTimeout: number; + public byteTimeout: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([ + Math.round(this.ackTimeout / 10), + Math.round(this.byteTimeout / 10), + ]); + return super.serialize(ctx); + } +} + +export interface SetSerialApiTimeoutsResponseOptions { + oldAckTimeout: number; + oldByteTimeout: number; +} + +@messageTypes(MessageType.Response, FunctionType.SetSerialApiTimeouts) +export class SetSerialApiTimeoutsResponse extends Message { + public constructor( + options: SetSerialApiTimeoutsResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.oldAckTimeout = options.oldAckTimeout; + this.oldByteTimeout = options.oldByteTimeout; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetSerialApiTimeoutsResponse { + const oldAckTimeout = raw.payload[0] * 10; + const oldByteTimeout = raw.payload[1] * 10; + + return new this({ + oldAckTimeout, + oldByteTimeout, + }); + } + + public oldAckTimeout: number; + public oldByteTimeout: number; +} diff --git a/packages/zwave-js/src/lib/serialapi/misc/SoftResetRequest.ts b/packages/serial/src/serialapi/misc/SoftResetRequest.ts similarity index 100% rename from packages/zwave-js/src/lib/serialapi/misc/SoftResetRequest.ts rename to packages/serial/src/serialapi/misc/SoftResetRequest.ts diff --git a/packages/zwave-js/src/lib/serialapi/misc/WatchdogMessages.ts b/packages/serial/src/serialapi/misc/WatchdogMessages.ts similarity index 100% rename from packages/zwave-js/src/lib/serialapi/misc/WatchdogMessages.ts rename to packages/serial/src/serialapi/misc/WatchdogMessages.ts diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts similarity index 69% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts rename to packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts index 3b8e7045fbb5..2d38b3998f6d 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AddNodeToNetworkRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/AddNodeToNetworkRequest.ts @@ -1,9 +1,11 @@ import { type BasicDeviceClass, type CommandClasses, + type ListenBehavior, type MessageOrCCLogEntry, MessagePriority, type MessageRecord, + type NodeId, NodeType, type NodeUpdatePayload, Protocols, @@ -11,18 +13,20 @@ import { 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, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, MessageOrigin, MessageType, expectedCallback, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -56,13 +60,13 @@ enum AddNodeFlags { ProtocolLongRange = 0x20, } -interface AddNodeToNetworkRequestOptions extends MessageBaseOptions { +export interface AddNodeToNetworkRequestOptions { addNodeType?: AddNodeType; highPower?: boolean; networkWide?: boolean; } -interface AddNodeDSKToNetworkRequestOptions extends MessageBaseOptions { +export interface AddNodeDSKToNetworkRequestOptions { nwiHomeId: Buffer; authHomeId: Buffer; highPower?: boolean; @@ -71,10 +75,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; @@ -92,23 +96,15 @@ 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) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== AddNodeToNetworkRequest - ) { - return new AddNodeToNetworkRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) - !== AddNodeToNetworkRequestStatusReport - ) { - return new AddNodeToNetworkRequestStatusReport(host, options); - } + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AddNodeToNetworkRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return AddNodeToNetworkRequest.from(raw, ctx); + } else { + return AddNodeToNetworkRequestStatusReport.from(raw, ctx); } - - super(host, options); } } @@ -142,24 +138,30 @@ function testCallbackForAddNodeRequest( @expectedCallback(testCallbackForAddNodeRequest) export class AddNodeToNetworkRequest extends AddNodeToNetworkRequestBase { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AddNodeToNetworkRequestOptions = {}, + options: AddNodeToNetworkRequestOptions & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - const config = this.payload[0]; - this.highPower = !!(config & AddNodeFlags.HighPower); - this.networkWide = !!(config & AddNodeFlags.NetworkWide); - this.addNodeType = config & 0b1111; - this.callbackId = this.payload[1]; - } else { - this.addNodeType = options.addNodeType; - this.highPower = !!options.highPower; - this.networkWide = !!options.networkWide; - } + super(options); + + this.addNodeType = options.addNodeType; + this.highPower = !!options.highPower; + this.networkWide = !!options.networkWide; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AddNodeToNetworkRequest { + const highPower = !!(raw.payload[0] & AddNodeFlags.HighPower); + const networkWide = !!(raw.payload[0] & AddNodeFlags.NetworkWide); + const addNodeType = raw.payload[0] & 0b1111; + const callbackId = raw.payload[1]; + + return new this({ + callbackId, + addNodeType, + highPower, + networkWide, + }); } /** The type of node to add */ @@ -169,14 +171,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 { @@ -205,14 +208,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 { @@ -227,10 +230,9 @@ export class EnableSmartStartListenRequest extends AddNodeToNetworkRequestBase { export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { public constructor( - host: ZWaveHost, - options: AddNodeDSKToNetworkRequestOptions, + options: AddNodeDSKToNetworkRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.nwiHomeId = options.nwiHomeId; this.authHomeId = options.authHomeId; @@ -249,7 +251,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; @@ -263,7 +266,7 @@ export class AddNodeDSKToNetworkRequest extends AddNodeToNetworkRequestBase { this.authHomeId, ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -306,51 +309,62 @@ export class AddNodeToNetworkRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | (AddNodeToNetworkRequestStatusReportOptions & MessageBaseOptions), + & AddNodeToNetworkRequestStatusReportOptions + & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.status = this.payload[1]; - switch (this.status) { - case AddNodeStatus.Ready: - case AddNodeStatus.NodeFound: - case AddNodeStatus.ProtocolDone: - case AddNodeStatus.Failed: - // no context for the status to parse - break; - - case AddNodeStatus.Done: { - const { nodeId } = parseNodeID( - this.payload, - host.nodeIdType, - 2, - ); - this.statusContext = { nodeId }; - break; - } - - case AddNodeStatus.AddingController: - case AddNodeStatus.AddingSlave: { - // the payload contains a node information frame - this.statusContext = parseNodeUpdatePayload( - this.payload.subarray(2), - host.nodeIdType, - ); - break; - } + super(options); + + this.status = options.status; + if ("nodeId" in options) { + this.statusContext = { nodeId: options.nodeId }; + } else if ("nodeInfo" in options) { + this.statusContext = options.nodeInfo; + } + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AddNodeToNetworkRequestStatusReport { + const callbackId = raw.payload[0]; + const status: AddNodeStatus = raw.payload[1]; + switch (status) { + case AddNodeStatus.Ready: + case AddNodeStatus.NodeFound: + case AddNodeStatus.ProtocolDone: + case AddNodeStatus.Failed: + // no context for the status to parse + return new this({ + callbackId, + status, + }); + + case AddNodeStatus.Done: { + const { nodeId } = parseNodeID( + raw.payload, + ctx.nodeIdType, + 2, + ); + return new this({ + callbackId, + status, + nodeId, + }); } - } else { - this.callbackId = options.callbackId; - this.status = options.status; - if ("nodeId" in options) { - this.statusContext = { nodeId: options.nodeId }; - } else if ("nodeInfo" in options) { - this.statusContext = options.nodeInfo; + + case AddNodeStatus.AddingController: + case AddNodeStatus.AddingSlave: { + // the payload contains a node information frame + const nodeInfo = parseNodeUpdatePayload( + raw.payload.subarray(2), + ctx.nodeIdType, + ); + return new this({ + callbackId, + status, + nodeInfo, + }); } } } @@ -364,18 +378,19 @@ export class AddNodeToNetworkRequestStatusReport public readonly status: AddNodeStatus; public readonly statusContext: AddNodeStatusContext | undefined; - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId, this.status]); if (this.statusContext?.basicDeviceClass != undefined) { this.payload = Buffer.concat([ this.payload, encodeNodeUpdatePayload( this.statusContext as NodeUpdatePayload, - this.host.nodeIdType, + ctx.nodeIdType, ), ]); } - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -383,7 +398,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/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts similarity index 53% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts rename to packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts index de5076baed1c..3cf86d202682 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignPriorityReturnRouteMessages.ts @@ -9,18 +9,18 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, expectedCallback, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -29,24 +29,22 @@ 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) { - if ( - gotDeserializationOptions(options) - && (new.target as any) - !== AssignPriorityReturnRouteRequestTransmitReport - ) { - return new AssignPriorityReturnRouteRequestTransmitReport( - host, - options, + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AssignPriorityReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return AssignPriorityReturnRouteRequest.from(raw, ctx); + } else { + return AssignPriorityReturnRouteRequestTransmitReport.from( + raw, + ctx, ); } - super(host, options); } } -export interface AssignPriorityReturnRouteRequestOptions - extends MessageBaseOptions -{ +export interface AssignPriorityReturnRouteRequestOptions { nodeId: number; destinationNodeId: number; repeaters: number[]; @@ -59,39 +57,41 @@ export class AssignPriorityReturnRouteRequest extends AssignPriorityReturnRouteRequestBase { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AssignPriorityReturnRouteRequestOptions, + options: AssignPriorityReturnRouteRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { + super(options); + if (options.nodeId === options.destinationNodeId) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The source and destination node must not be identical`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + if ( + options.repeaters.length > MAX_REPEATERS + || options.repeaters.some((id) => id < 1 || id > MAX_NODES) + ) { + throw new ZWaveError( + `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.nodeId === options.destinationNodeId) { - throw new ZWaveError( - `The source and destination node must not be identical`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - if ( - options.repeaters.length > MAX_REPEATERS - || options.repeaters.some((id) => id < 1 || id > MAX_NODES) - ) { - throw new ZWaveError( - `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.nodeId = options.nodeId; - this.destinationNodeId = options.destinationNodeId; - this.repeaters = options.repeaters; - this.routeSpeed = options.routeSpeed; } + + this.nodeId = options.nodeId; + this.destinationNodeId = options.destinationNodeId; + this.repeaters = options.repeaters; + this.routeSpeed = options.routeSpeed; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPriorityReturnRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new AssignPriorityReturnRouteRequest({}); } public nodeId: number; @@ -99,11 +99,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 +119,7 @@ export class AssignPriorityReturnRouteRequest ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -134,22 +135,38 @@ export class AssignPriorityReturnRouteRequest ZWaveDataRate, this.routeSpeed, ), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } } +export interface AssignPriorityReturnRouteResponseOptions { + hasStarted: boolean; +} + @messageTypes(MessageType.Response, FunctionType.AssignPriorityReturnRoute) export class AssignPriorityReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: AssignPriorityReturnRouteResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.hasStarted = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.hasStarted = options.hasStarted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPriorityReturnRouteResponse { + const hasStarted = raw.payload[0] !== 0; + + return new this({ + hasStarted, + }); } public isOK(): boolean { @@ -166,20 +183,38 @@ export class AssignPriorityReturnRouteResponse extends Message } } +export interface AssignPriorityReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + export class AssignPriorityReturnRouteRequestTransmitReport extends AssignPriorityReturnRouteRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & AssignPriorityReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); this.callbackId = this.payload[0]; this.transmitStatus = this.payload[1]; } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPriorityReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + public isOK(): boolean { // The other statuses are technically "not OK", but they are caused by // not being able to contact the node. We don't want the node to be marked @@ -193,7 +228,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/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts similarity index 51% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts rename to packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts index 8d3c8a3eb367..80c1907b8a1e 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages.ts @@ -9,18 +9,18 @@ import { ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, expectedCallback, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -29,24 +29,22 @@ 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) { - if ( - gotDeserializationOptions(options) - && (new.target as any) - !== AssignPrioritySUCReturnRouteRequestTransmitReport - ) { - return new AssignPrioritySUCReturnRouteRequestTransmitReport( - host, - options, + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AssignPrioritySUCReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return AssignPrioritySUCReturnRouteRequest.from(raw, ctx); + } else { + return AssignPrioritySUCReturnRouteRequestTransmitReport.from( + raw, + ctx, ); } - super(host, options); } } -export interface AssignPrioritySUCReturnRouteRequestOptions - extends MessageBaseOptions -{ +export interface AssignPrioritySUCReturnRouteRequestOptions { nodeId: number; repeaters: number[]; routeSpeed: ZWaveDataRate; @@ -58,40 +56,45 @@ export class AssignPrioritySUCReturnRouteRequest extends AssignPrioritySUCReturnRouteRequestBase { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | AssignPrioritySUCReturnRouteRequestOptions, + & AssignPrioritySUCReturnRouteRequestOptions + & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { + super(options); + if ( + options.repeaters.length > MAX_REPEATERS + || options.repeaters.some((id) => id < 1 || id > MAX_NODES) + ) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if ( - options.repeaters.length > MAX_REPEATERS - || options.repeaters.some((id) => id < 1 || id > MAX_NODES) - ) { - throw new ZWaveError( - `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.nodeId = options.nodeId; - this.repeaters = options.repeaters; - this.routeSpeed = options.routeSpeed; } + + this.nodeId = options.nodeId; + this.repeaters = options.repeaters; + this.routeSpeed = options.routeSpeed; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPrioritySUCReturnRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new AssignPrioritySUCReturnRouteRequest({}); } public nodeId: number; 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 +107,7 @@ export class AssignPrioritySUCReturnRouteRequest ]), ]); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -119,22 +122,40 @@ export class AssignPrioritySUCReturnRouteRequest ZWaveDataRate, this.routeSpeed, ), - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", }, }; } } +export interface AssignPrioritySUCReturnRouteResponseOptions { + hasStarted: boolean; +} + @messageTypes(MessageType.Response, FunctionType.AssignPrioritySUCReturnRoute) export class AssignPrioritySUCReturnRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & AssignPrioritySUCReturnRouteResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.hasStarted = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.hasStarted = options.hasStarted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPrioritySUCReturnRouteResponse { + const hasStarted = raw.payload[0] !== 0; + + return new this({ + hasStarted, + }); } public isOK(): boolean { @@ -151,18 +172,37 @@ export class AssignPrioritySUCReturnRouteResponse extends Message } } +export interface AssignPrioritySUCReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + export class AssignPrioritySUCReturnRouteRequestTransmitReport extends AssignPrioritySUCReturnRouteRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & AssignPrioritySUCReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignPrioritySUCReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); } public isOK(): boolean { @@ -178,7 +218,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/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts new file mode 100644 index 000000000000..2c2e9d484b23 --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/AssignReturnRouteMessages.ts @@ -0,0 +1,194 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitStatus, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, +} from "@zwave-js/core"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; +import { + FunctionType, + Message, + type MessageBaseOptions, + MessageOrigin, + MessageType, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.AssignReturnRoute) +@priority(MessagePriority.Normal) +export class AssignReturnRouteRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AssignReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return AssignReturnRouteRequest.from(raw, ctx); + } else { + return AssignReturnRouteRequestTransmitReport.from(raw, ctx); + } + } +} + +export interface AssignReturnRouteRequestOptions { + nodeId: number; + destinationNodeId: number; +} + +@expectedResponse(FunctionType.AssignReturnRoute) +@expectedCallback(FunctionType.AssignReturnRoute) +export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase { + public constructor( + options: AssignReturnRouteRequestOptions & MessageBaseOptions, + ) { + super(options); + if (options.nodeId === options.destinationNodeId) { + throw new ZWaveError( + `The source and destination node must not be identical`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.nodeId = options.nodeId; + this.destinationNodeId = options.destinationNodeId; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignReturnRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new AssignReturnRouteRequest({}); + } + + public nodeId: number; + public destinationNodeId: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.nodeId, ctx.nodeIdType); + const destinationNodeId = encodeNodeID( + this.destinationNodeId, + ctx.nodeIdType, + ); + + this.payload = Buffer.concat([ + nodeId, + destinationNodeId, + Buffer.from([this.callbackId]), + ]); + + return super.serialize(ctx); + } +} + +export interface AssignReturnRouteResponseOptions { + hasStarted: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.AssignReturnRoute) +export class AssignReturnRouteResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: AssignReturnRouteResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.hasStarted = options.hasStarted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignReturnRouteResponse { + const hasStarted = raw.payload[0] !== 0; + + return new this({ + hasStarted, + }); + } + + public isOK(): boolean { + return this.hasStarted; + } + + public readonly hasStarted: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "has started": this.hasStarted }, + }; + } +} + +export interface AssignReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class AssignReturnRouteRequestTransmitReport + extends AssignReturnRouteRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & AssignReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public isOK(): boolean { + // The other statuses are technically "not OK", but they are caused by + // not being able to contact the node. We don't want the node to be marked + // as dead because of that + return this.transmitStatus !== TransmitStatus.NoAck; + } + + public readonly transmitStatus: TransmitStatus; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} diff --git a/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts new file mode 100644 index 000000000000..b85a0edec06c --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts @@ -0,0 +1,198 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitStatus, + encodeNodeID, +} from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, + MessageType, + type SuccessIndicator, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.AssignSUCReturnRoute) +@priority(MessagePriority.Normal) +export class AssignSUCReturnRouteRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): AssignSUCReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return AssignSUCReturnRouteRequest.from(raw, ctx); + } else { + return AssignSUCReturnRouteRequestTransmitReport.from(raw, ctx); + } + } +} + +export interface AssignSUCReturnRouteRequestOptions { + nodeId: number; + disableCallbackFunctionTypeCheck?: boolean; +} + +function testAssignSUCReturnRouteCallback( + sent: AssignSUCReturnRouteRequest, + callback: Message, +): boolean { + // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute + if (sent.disableCallbackFunctionTypeCheck) { + return true; + } + return callback.functionType === FunctionType.AssignSUCReturnRoute; +} + +@expectedResponse(FunctionType.AssignSUCReturnRoute) +@expectedCallback(testAssignSUCReturnRouteCallback) +export class AssignSUCReturnRouteRequest + extends AssignSUCReturnRouteRequestBase +{ + public constructor( + options: AssignSUCReturnRouteRequestOptions & MessageBaseOptions, + ) { + super(options); + this.nodeId = options.nodeId; + this.disableCallbackFunctionTypeCheck = + options.disableCallbackFunctionTypeCheck; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignSUCReturnRouteRequest { + const nodeId = raw.payload[0]; + const callbackId = raw.payload[1]; + + return new this({ + nodeId, + callbackId, + }); + } + + public nodeId: number; + public readonly disableCallbackFunctionTypeCheck?: boolean; + + 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(ctx); + } +} + +export interface AssignSUCReturnRouteResponseOptions { + wasExecuted: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.AssignSUCReturnRoute) +export class AssignSUCReturnRouteResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: AssignSUCReturnRouteResponseOptions & MessageBaseOptions, + ) { + super(options); + this.wasExecuted = options.wasExecuted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignSUCReturnRouteResponse { + const wasExecuted = raw.payload[0] !== 0; + + return new this({ + wasExecuted, + }); + } + + public isOK(): boolean { + return this.wasExecuted; + } + + public wasExecuted: boolean; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was executed": this.wasExecuted }, + }; + } +} + +export interface AssignSUCReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class AssignSUCReturnRouteRequestTransmitReport + extends AssignSUCReturnRouteRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & AssignSUCReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): AssignSUCReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public isOK(): boolean { + // The other statuses are technically "not OK", but they are caused by + // not being able to contact the node. We don't want the node to be marked + // as dead because of that + return this.transmitStatus !== TransmitStatus.NoAck; + } + + public transmitStatus: TransmitStatus; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} diff --git a/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts new file mode 100644 index 000000000000..cf1eb46ecbe9 --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/DeleteReturnRouteMessages.ts @@ -0,0 +1,176 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitStatus, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, +} from "@zwave-js/core"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; +import { + FunctionType, + Message, + type MessageBaseOptions, + MessageOrigin, + MessageType, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.DeleteReturnRoute) +@priority(MessagePriority.Normal) +export class DeleteReturnRouteRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): DeleteReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return DeleteReturnRouteRequest.from(raw, ctx); + } else { + return DeleteReturnRouteRequestTransmitReport.from(raw, ctx); + } + } +} + +export interface DeleteReturnRouteRequestOptions { + nodeId: number; +} + +@expectedResponse(FunctionType.DeleteReturnRoute) +@expectedCallback(FunctionType.DeleteReturnRoute) +export class DeleteReturnRouteRequest extends DeleteReturnRouteRequestBase { + public constructor( + options: DeleteReturnRouteRequestOptions & MessageBaseOptions, + ) { + super(options); + this.nodeId = options.nodeId; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteReturnRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new DeleteReturnRouteRequest({}); + } + + public nodeId: number; + + 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(ctx); + } +} + +export interface DeleteReturnRouteResponseOptions { + hasStarted: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.DeleteReturnRoute) +export class DeleteReturnRouteResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: DeleteReturnRouteResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.hasStarted = options.hasStarted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteReturnRouteResponse { + const hasStarted = raw.payload[0] !== 0; + + return new this({ + hasStarted, + }); + } + + public isOK(): boolean { + return this.hasStarted; + } + + public readonly hasStarted: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "has started": this.hasStarted }, + }; + } +} + +export interface DeleteReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class DeleteReturnRouteRequestTransmitReport + extends DeleteReturnRouteRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & DeleteReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public isOK(): boolean { + // The other statuses are technically "not OK", but they are caused by + // not being able to contact the node. We don't want the node to be marked + // as dead because of that + return this.transmitStatus !== TransmitStatus.NoAck; + } + + public readonly transmitStatus: TransmitStatus; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} diff --git a/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts new file mode 100644 index 000000000000..be76caf364cc --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts @@ -0,0 +1,200 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitStatus, + encodeNodeID, +} from "@zwave-js/core"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; +import { + FunctionType, + Message, + type MessageBaseOptions, + MessageOrigin, + MessageType, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.DeleteSUCReturnRoute) +@priority(MessagePriority.Normal) +export class DeleteSUCReturnRouteRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): DeleteSUCReturnRouteRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return DeleteSUCReturnRouteRequest.from(raw, ctx); + } else { + return DeleteSUCReturnRouteRequestTransmitReport.from(raw, ctx); + } + } +} + +export interface DeleteSUCReturnRouteRequestOptions { + nodeId: number; + disableCallbackFunctionTypeCheck?: boolean; +} + +function testDeleteSUCReturnRouteCallback( + sent: DeleteSUCReturnRouteRequest, + callback: Message, +): boolean { + // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute + if (sent.disableCallbackFunctionTypeCheck) { + return true; + } + return callback.functionType === FunctionType.DeleteSUCReturnRoute; +} + +@expectedResponse(FunctionType.DeleteSUCReturnRoute) +@expectedCallback(testDeleteSUCReturnRouteCallback) +export class DeleteSUCReturnRouteRequest + extends DeleteSUCReturnRouteRequestBase +{ + public constructor( + options: DeleteSUCReturnRouteRequestOptions & MessageBaseOptions, + ) { + super(options); + this.nodeId = options.nodeId; + this.disableCallbackFunctionTypeCheck = + options.disableCallbackFunctionTypeCheck; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteSUCReturnRouteRequest { + const nodeId = raw.payload[0]; + const callbackId = raw.payload[1]; + + return new this({ + nodeId, + callbackId, + }); + } + + public nodeId: number; + public readonly disableCallbackFunctionTypeCheck?: boolean; + + 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(ctx); + } +} + +export interface DeleteSUCReturnRouteResponseOptions { + wasExecuted: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.DeleteSUCReturnRoute) +export class DeleteSUCReturnRouteResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: DeleteSUCReturnRouteResponseOptions & MessageBaseOptions, + ) { + super(options); + this.wasExecuted = options.wasExecuted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteSUCReturnRouteResponse { + const wasExecuted = raw.payload[0] !== 0; + + return new this({ + wasExecuted, + }); + } + + public isOK(): boolean { + return this.wasExecuted; + } + + public readonly wasExecuted: boolean; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was executed": this.wasExecuted }, + }; + } +} + +export interface DeleteSUCReturnRouteRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class DeleteSUCReturnRouteRequestTransmitReport + extends DeleteSUCReturnRouteRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & DeleteSUCReturnRouteRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): DeleteSUCReturnRouteRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public isOK(): boolean { + // The other statuses are technically "not OK", but they are caused by + // not being able to contact the node. We don't want the node to be marked + // as dead because of that + return this.transmitStatus !== TransmitStatus.NoAck; + } + + public readonly transmitStatus: TransmitStatus; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts similarity index 100% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts rename to packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages._test.ts diff --git a/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts new file mode 100644 index 000000000000..82280bbad8f9 --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts @@ -0,0 +1,193 @@ +import { + type BasicDeviceClass, + type DataRate, + type FLiRS, + MessagePriority, + type NodeProtocolInfoAndDeviceClass, + type NodeType, + type ProtocolVersion, + encodeNodeID, + encodeNodeProtocolInfo, + isLongRangeNodeId, + parseNodeID, + parseNodeProtocolInfo, +} from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { isObject } from "alcalzone-shared/typeguards"; + +export interface GetNodeProtocolInfoRequestOptions { + requestedNodeId: number; +} + +@messageTypes(MessageType.Request, FunctionType.GetNodeProtocolInfo) +@expectedResponse(FunctionType.GetNodeProtocolInfo) +@priority(MessagePriority.Controller) +export class GetNodeProtocolInfoRequest extends Message { + public constructor( + options: GetNodeProtocolInfoRequestOptions & MessageBaseOptions, + ) { + super(options); + this.requestedNodeId = options.requestedNodeId; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): GetNodeProtocolInfoRequest { + const requestedNodeId = + parseNodeID(raw.payload, ctx.nodeIdType, 0).nodeId; + + return new this({ + requestedNodeId, + }); + } + + // This must not be called nodeId or the message will be treated as a node query + // but this is a message to the controller + public requestedNodeId: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = encodeNodeID(this.requestedNodeId, ctx.nodeIdType); + return super.serialize(ctx); + } +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface GetNodeProtocolInfoResponseOptions + extends NodeProtocolInfoAndDeviceClass +{} + +@messageTypes(MessageType.Response, FunctionType.GetNodeProtocolInfo) +export class GetNodeProtocolInfoResponse extends Message { + public constructor( + options: GetNodeProtocolInfoResponseOptions & MessageBaseOptions, + ) { + super(options); + + this.isListening = options.isListening; + this.isFrequentListening = options.isFrequentListening; + this.isRouting = options.isRouting; + this.supportedDataRates = options.supportedDataRates; + this.protocolVersion = options.protocolVersion; + this.optionalFunctionality = options.optionalFunctionality; + this.nodeType = options.nodeType; + this.supportsSecurity = options.supportsSecurity; + this.supportsBeaming = options.supportsBeaming; + this.basicDeviceClass = options.basicDeviceClass; + this.genericDeviceClass = options.genericDeviceClass; + this.specificDeviceClass = options.specificDeviceClass; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): GetNodeProtocolInfoResponse { + // The context should contain the node ID the protocol info was requested for. + // We use it here to determine whether the node is long range. + let isLongRange = false; + const requestContext = ctx.requestStorage?.get( + FunctionType.GetNodeProtocolInfo, + ); + if ( + isObject(requestContext) + && "nodeId" in requestContext + && typeof requestContext.nodeId === "number" + ) { + isLongRange = isLongRangeNodeId(requestContext.nodeId); + } + + const { hasSpecificDeviceClass, ...rest } = parseNodeProtocolInfo( + raw.payload, + 0, + isLongRange, + ); + const isListening: boolean = rest.isListening; + const isFrequentListening: FLiRS = rest.isFrequentListening; + const isRouting: boolean = rest.isRouting; + const supportedDataRates: DataRate[] = rest.supportedDataRates; + const protocolVersion: ProtocolVersion = rest.protocolVersion; + const optionalFunctionality: boolean = rest.optionalFunctionality; + const nodeType: NodeType = rest.nodeType; + const supportsSecurity: boolean = rest.supportsSecurity; + const supportsBeaming: boolean = rest.supportsBeaming; + + // parse the device class + const basicDeviceClass: BasicDeviceClass = raw.payload[3]; + const genericDeviceClass = raw.payload[4]; + const specificDeviceClass = hasSpecificDeviceClass + ? raw.payload[5] + : 0x00; + + return new this({ + isListening, + isFrequentListening, + isRouting, + supportedDataRates, + protocolVersion, + optionalFunctionality, + nodeType, + supportsSecurity, + supportsBeaming, + basicDeviceClass, + genericDeviceClass, + specificDeviceClass, + }); + } + + /** Whether this node is always listening or not */ + public isListening: boolean; + /** Indicates the wakeup interval if this node is a FLiRS node. `false` if it isn't. */ + public isFrequentListening: FLiRS; + /** Whether the node supports routing/forwarding messages. */ + public isRouting: boolean; + public supportedDataRates: DataRate[]; + public protocolVersion: ProtocolVersion; + /** Whether this node supports additional CCs besides the mandatory minimum */ + public optionalFunctionality: boolean; + /** Whether this node is a controller (can calculate routes) or an end node (relies on route info) */ + public nodeType: NodeType; + /** Whether this node supports security (S0 or S2) */ + public supportsSecurity: boolean; + /** Whether this node can issue wakeup beams to FLiRS nodes */ + public supportsBeaming: boolean; + + public basicDeviceClass: BasicDeviceClass; + public genericDeviceClass: number; + public specificDeviceClass: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + const protocolInfo = encodeNodeProtocolInfo({ + isListening: this.isListening, + isFrequentListening: this.isFrequentListening, + isRouting: this.isRouting, + supportedDataRates: this.supportedDataRates, + protocolVersion: this.protocolVersion, + optionalFunctionality: this.optionalFunctionality, + nodeType: this.nodeType, + supportsSecurity: this.supportsSecurity, + supportsBeaming: this.supportsBeaming, + hasSpecificDeviceClass: this.specificDeviceClass !== 0, + }); + this.payload = Buffer.concat([ + protocolInfo, + Buffer.from([ + this.basicDeviceClass, + this.genericDeviceClass, + this.specificDeviceClass, + ]), + ]); + + return super.serialize(ctx); + } +} diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts similarity index 54% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts rename to packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts index ab2705c82e39..22705b08cefe 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetPriorityRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetPriorityRouteMessages.ts @@ -10,21 +10,21 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; import { getEnumMemberName } from "@zwave-js/shared"; -export interface GetPriorityRouteRequestOptions extends MessageBaseOptions { +export interface GetPriorityRouteRequestOptions { destinationNodeId: number; } @@ -33,29 +33,33 @@ export interface GetPriorityRouteRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.GetPriorityRoute) export class GetPriorityRouteRequest extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | GetPriorityRouteRequestOptions, + options: GetPriorityRouteRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.destinationNodeId = options.destinationNodeId; - } + super(options); + this.destinationNodeId = options.destinationNodeId; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetPriorityRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new GetPriorityRouteRequest({}); } 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 { @@ -68,28 +72,55 @@ export class GetPriorityRouteRequest extends Message { } } +export interface GetPriorityRouteResponseOptions { + destinationNodeId: number; + routeKind: RouteKind; + repeaters?: number[]; + routeSpeed?: ZWaveDataRate; +} + @messageTypes(MessageType.Response, FunctionType.GetPriorityRoute) export class GetPriorityRouteResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: GetPriorityRouteResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.destinationNodeId = options.destinationNodeId; + this.routeKind = options.routeKind; + this.repeaters = options.repeaters; + this.routeSpeed = options.routeSpeed; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): GetPriorityRouteResponse { let offset = 0; const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, + raw.payload, + ctx.nodeIdType, offset, ); offset += nodeIdBytes; - this.destinationNodeId = nodeId; - this.routeKind = this.payload[offset++]; - if (this.routeKind) { - this.repeaters = [ - ...this.payload.subarray(offset, offset + MAX_REPEATERS), + const destinationNodeId = nodeId; + const routeKind: RouteKind = raw.payload[offset++]; + let repeaters: number[] | undefined; + let routeSpeed: ZWaveDataRate | undefined; + if (routeKind) { + repeaters = [ + ...raw.payload.subarray(offset, offset + MAX_REPEATERS), ].filter((id) => id > 0); - this.routeSpeed = this.payload[offset + MAX_REPEATERS]; + routeSpeed = raw.payload[offset + MAX_REPEATERS]; } + + return new this({ + destinationNodeId, + routeKind, + repeaters, + routeSpeed, + }); } public readonly destinationNodeId: number; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts similarity index 62% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts rename to packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts index 26431a51ee79..e2b8b98ea046 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetRoutingInfoMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetRoutingInfoMessages.ts @@ -5,19 +5,20 @@ import { encodeNodeID, parseNodeBitMask, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, messageTypes, priority, } from "@zwave-js/serial"; -interface GetRoutingInfoRequestOptions extends MessageBaseOptions { +export interface GetRoutingInfoRequestOptions { nodeId: number; removeNonRepeaters?: boolean; removeBadLinks?: boolean; @@ -27,8 +28,10 @@ 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 & MessageBaseOptions, + ) { + super(options); this.sourceNodeId = options.nodeId; this.removeNonRepeaters = !!options.removeNonRepeaters; this.removeBadLinks = !!options.removeBadLinks; @@ -38,8 +41,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 +52,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 { @@ -63,31 +66,44 @@ export class GetRoutingInfoRequest extends Message { } } +export interface GetRoutingInfoResponseOptions { + nodeIds: number[]; +} + @messageTypes(MessageType.Response, FunctionType.GetRoutingInfo) export class GetRoutingInfoResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: GetRoutingInfoResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - if (this.payload.length === NUM_NODEMASK_BYTES) { + // TODO: Check implementation: + this.nodeIds = options.nodeIds; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetRoutingInfoResponse { + let nodeIds: number[]; + if (raw.payload.length === NUM_NODEMASK_BYTES) { // the payload contains a bit mask of all neighbor nodes - this._nodeIds = parseNodeBitMask(this.payload); + nodeIds = parseNodeBitMask(raw.payload); } else { - this._nodeIds = []; + nodeIds = []; } - } - private _nodeIds: number[]; - public get nodeIds(): number[] { - return this._nodeIds; + return new this({ + nodeIds, + }); } + public nodeIds: number[]; + public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), - message: { "node ids": `${this._nodeIds.join(", ")}` }, + message: { "node ids": `${this.nodeIds.join(", ")}` }, }; } } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts b/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts similarity index 52% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts rename to packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts index 31402458a911..5726b1097d6b 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetSUCNodeIdMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/GetSUCNodeIdMessages.ts @@ -1,13 +1,13 @@ import { MessagePriority, encodeNodeID, parseNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -17,34 +17,40 @@ import { @priority(MessagePriority.Controller) export class GetSUCNodeIdRequest extends Message {} -export interface GetSUCNodeIdResponseOptions extends MessageBaseOptions { +export interface GetSUCNodeIdResponseOptions { sucNodeId: number; } @messageTypes(MessageType.Response, FunctionType.GetSUCNodeId) export class GetSUCNodeIdResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | GetSUCNodeIdResponseOptions, + options: GetSUCNodeIdResponseOptions & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.sucNodeId = parseNodeID( - this.payload, - this.host.nodeIdType, - 0, - ).nodeId; - } else { - this.sucNodeId = options.sucNodeId; - } + super(options); + + this.sucNodeId = options.sucNodeId; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): GetSUCNodeIdResponse { + const sucNodeId = parseNodeID( + raw.payload, + ctx.nodeIdType, + 0, + ).nodeId; + + return new this({ + sucNodeId, + }); } /** 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/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts similarity index 52% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts rename to packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts index 6960e2db01da..f7e262908e93 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/IsFailedNodeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/IsFailedNodeMessages.ts @@ -1,17 +1,18 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, messageTypes, priority, } from "@zwave-js/serial"; -export interface IsFailedNodeRequestOptions extends MessageBaseOptions { +export interface IsFailedNodeRequestOptions { // This must not be called nodeId or rejectAllTransactions may reject the request failedNodeId: number; } @@ -20,28 +21,46 @@ 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 & MessageBaseOptions, + ) { + 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); } } +export interface IsFailedNodeResponseOptions { + result: boolean; +} + @messageTypes(MessageType.Response, FunctionType.IsFailedNode) export class IsFailedNodeResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: IsFailedNodeResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.result = !!this.payload[0]; + super(options); + + // TODO: Check implementation: + this.result = options.result; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): IsFailedNodeResponse { + const result = !!raw.payload[0]; + + return new this({ + result, + }); } public readonly result: boolean; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts b/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts similarity index 53% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts rename to packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts index 8b8e96e39d82..3fd45fa50097 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveFailedNodeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RemoveFailedNodeMessages.ts @@ -1,16 +1,18 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + MessageOrigin, MessageType, expectedCallback, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -44,18 +46,19 @@ export enum RemoveFailedNodeStatus { @messageTypes(MessageType.Request, FunctionType.RemoveFailedNode) @priority(MessagePriority.Controller) export class RemoveFailedNodeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== RemoveFailedNodeRequestStatusReport - ) { - return new RemoveFailedNodeRequestStatusReport(host, options); + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): RemoveFailedNodeRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return RemoveFailedNodeRequest.from(raw, ctx); + } else { + return RemoveFailedNodeRequestStatusReport.from(raw, ctx); } - super(host, options); } } -interface RemoveFailedNodeRequestOptions extends MessageBaseOptions { +export interface RemoveFailedNodeRequestOptions { // This must not be called nodeId or rejectAllTransactions may reject the request failedNodeId: number; } @@ -64,10 +67,9 @@ interface RemoveFailedNodeRequestOptions extends MessageBaseOptions { @expectedCallback(FunctionType.RemoveFailedNode) export class RemoveFailedNodeRequest extends RemoveFailedNodeRequestBase { public constructor( - host: ZWaveHost, - options: RemoveFailedNodeRequestOptions, + options: RemoveFailedNodeRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.failedNodeId = options.failedNodeId; } @@ -75,55 +77,85 @@ 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); } } +export interface RemoveFailedNodeRequestStatusReportOptions { + removeStatus: RemoveFailedNodeStatus; +} + export class RemoveFailedNodeRequestStatusReport extends RemoveFailedNodeRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & RemoveFailedNodeRequestStatusReportOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); - this.callbackId = this.payload[0]; - this._removeStatus = this.payload[1]; + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.removeStatus = options.removeStatus; } - private _removeStatus: RemoveFailedNodeStatus; - public get removeStatus(): RemoveFailedNodeStatus { - return this._removeStatus; + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): RemoveFailedNodeRequestStatusReport { + const callbackId = raw.payload[0]; + const removeStatus: RemoveFailedNodeStatus = raw.payload[1]; + + return new this({ + callbackId, + removeStatus, + }); } + public removeStatus: RemoveFailedNodeStatus; + public isOK(): boolean { - return this._removeStatus === RemoveFailedNodeStatus.NodeRemoved; + return this.removeStatus === RemoveFailedNodeStatus.NodeRemoved; } } +export interface RemoveFailedNodeResponseOptions { + removeStatus: RemoveFailedNodeStartFlags; +} + @messageTypes(MessageType.Response, FunctionType.RemoveFailedNode) export class RemoveFailedNodeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: RemoveFailedNodeResponseOptions & MessageBaseOptions, ) { - super(host, options); - this._removeStatus = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.removeStatus = options.removeStatus; } - private _removeStatus: RemoveFailedNodeStartFlags; - public get removeStatus(): RemoveFailedNodeStartFlags { - return this._removeStatus; + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): RemoveFailedNodeResponse { + const removeStatus: RemoveFailedNodeStartFlags = raw.payload[0]; + + return new this({ + removeStatus, + }); } + public removeStatus: RemoveFailedNodeStartFlags; + public isOK(): boolean { - return this._removeStatus === RemoveFailedNodeStartFlags.OK; + return this.removeStatus === RemoveFailedNodeStartFlags.OK; } } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts b/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts similarity index 56% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts rename to packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts index afab88dc9a64..5bed8a59a5b6 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/RemoveNodeFromNetworkRequest.ts @@ -4,18 +4,19 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, MessageOrigin, MessageType, expectedCallback, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -43,7 +44,7 @@ enum RemoveNodeFlags { NetworkWide = 0x40, } -interface RemoveNodeFromNetworkRequestOptions extends MessageBaseOptions { +export interface RemoveNodeFromNetworkRequestOptions { removeNodeType?: RemoveNodeType; highPower?: boolean; networkWide?: boolean; @@ -53,26 +54,15 @@ 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) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== RemoveNodeFromNetworkRequest - ) { - return new RemoveNodeFromNetworkRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) - !== RemoveNodeFromNetworkRequestStatusReport - ) { - return new RemoveNodeFromNetworkRequestStatusReport( - host, - options, - ); - } + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): RemoveNodeFromNetworkRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return RemoveNodeFromNetworkRequest.from(raw, ctx); + } else { + return RemoveNodeFromNetworkRequestStatusReport.from(raw, ctx); } - - super(host, options); } } @@ -108,24 +98,30 @@ export class RemoveNodeFromNetworkRequest extends RemoveNodeFromNetworkRequestBase { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | RemoveNodeFromNetworkRequestOptions = {}, + options: RemoveNodeFromNetworkRequestOptions & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - const config = this.payload[0]; - this.highPower = !!(config & RemoveNodeFlags.HighPower); - this.networkWide = !!(config & RemoveNodeFlags.NetworkWide); - this.removeNodeType = config & 0b11111; - this.callbackId = this.payload[1]; - } else { - this.removeNodeType = options.removeNodeType; - this.highPower = !!options.highPower; - this.networkWide = !!options.networkWide; - } + super(options); + + this.removeNodeType = options.removeNodeType; + this.highPower = !!options.highPower; + this.networkWide = !!options.networkWide; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): RemoveNodeFromNetworkRequest { + const highPower = !!(raw.payload[0] & RemoveNodeFlags.HighPower); + const networkWide = !!(raw.payload[0] & RemoveNodeFlags.NetworkWide); + const removeNodeType = raw.payload[0] & 0b11111; + const callbackId = raw.payload[1]; + + return new this({ + callbackId, + removeNodeType, + highPower, + networkWide, + }); } /** The type of node to remove */ @@ -135,14 +131,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); } } @@ -151,6 +148,7 @@ export type RemoveNodeFromNetworkRequestStatusReportOptions = { | RemoveNodeStatus.Ready | RemoveNodeStatus.NodeFound | RemoveNodeStatus.Failed + | RemoveNodeStatus.Reserved_0x05 | RemoveNodeStatus.Done; } | { status: @@ -164,71 +162,77 @@ export class RemoveNodeFromNetworkRequestStatusReport implements SuccessIndicator { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | ( - & RemoveNodeFromNetworkRequestStatusReportOptions - & MessageBaseOptions - ), + & RemoveNodeFromNetworkRequestStatusReportOptions + & MessageBaseOptions, ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.status = this.payload[1]; - switch (this.status) { - case RemoveNodeStatus.Ready: - case RemoveNodeStatus.NodeFound: - case RemoveNodeStatus.Failed: - case RemoveNodeStatus.Done: - // no context for the status to parse - // TODO: - // An application MUST time out waiting for the REMOVE_NODE_STATUS_REMOVING_SLAVE status - // if it does not receive the indication within a 14 sec after receiving the - // REMOVE_NODE_STATUS_NODE_FOUND status. - break; - - case RemoveNodeStatus.RemovingController: - case RemoveNodeStatus.RemovingSlave: { - // the payload contains the node ID - const { nodeId } = parseNodeID( - this.payload.subarray(2), - this.host.nodeIdType, - ); - this.statusContext = { nodeId }; - break; - } - } - } else { - this.callbackId = options.callbackId; - this.status = options.status; - if ("nodeId" in options) { - this.statusContext = { nodeId: options.nodeId }; + super(options); + + this.status = options.status; + if ("nodeId" in options) { + this.statusContext = { nodeId: options.nodeId }; + } + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): RemoveNodeFromNetworkRequestStatusReport { + const callbackId = raw.payload[0]; + const status: RemoveNodeStatus = raw.payload[1]; + switch (status) { + case RemoveNodeStatus.Ready: + case RemoveNodeStatus.NodeFound: + case RemoveNodeStatus.Failed: + case RemoveNodeStatus.Reserved_0x05: + case RemoveNodeStatus.Done: + // no context for the status to parse + // TODO: + // An application MUST time out waiting for the REMOVE_NODE_STATUS_REMOVING_SLAVE status + // if it does not receive the indication within a 14 sec after receiving the + // REMOVE_NODE_STATUS_NODE_FOUND status. + return new this({ + callbackId, + status, + }); + + case RemoveNodeStatus.RemovingController: + case RemoveNodeStatus.RemovingSlave: { + // the payload contains the node ID + const { nodeId } = parseNodeID( + raw.payload.subarray(2), + ctx.nodeIdType, + ); + return new this({ + callbackId, + status, + nodeId, + }); } } } + public readonly status: RemoveNodeStatus; + public readonly statusContext: RemoveNodeStatusContext | undefined; + isOK(): boolean { // Some of the status codes are for unsolicited callbacks, but // Failed is the only NOK status. return this.status !== RemoveNodeStatus.Failed; } - public serialize(): Buffer { + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); this.payload = Buffer.from([this.callbackId, this.status]); if (this.statusContext?.nodeId != undefined) { this.payload = Buffer.concat([ this.payload, - encodeNodeID(this.statusContext.nodeId, this.host.nodeIdType), + encodeNodeID(this.statusContext.nodeId, ctx.nodeIdType), ]); } - return super.serialize(); + return super.serialize(ctx); } - - public readonly status: RemoveNodeStatus; - public readonly statusContext: RemoveNodeStatusContext | undefined; } interface RemoveNodeStatusContext { diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts b/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts similarity index 54% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts rename to packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts index 1e3f979b02f8..cb7e4ad9932e 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts +++ b/packages/serial/src/serialapi/network-mgmt/ReplaceFailedNodeRequest.ts @@ -1,15 +1,17 @@ import { MessagePriority, encodeNodeID } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + MessageOrigin, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -44,18 +46,19 @@ export enum ReplaceFailedNodeStatus { @messageTypes(MessageType.Request, FunctionType.ReplaceFailedNode) @priority(MessagePriority.Controller) export class ReplaceFailedNodeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== ReplaceFailedNodeRequestStatusReport - ) { - return new ReplaceFailedNodeRequestStatusReport(host, options); + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): ReplaceFailedNodeRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return ReplaceFailedNodeRequest.from(raw, ctx); + } else { + return ReplaceFailedNodeRequestStatusReport.from(raw, ctx); } - super(host, options); } } -interface ReplaceFailedNodeRequestOptions extends MessageBaseOptions { +export interface ReplaceFailedNodeRequestOptions { // This must not be called nodeId or rejectAllTransactions may reject the request failedNodeId: number; } @@ -63,10 +66,9 @@ interface ReplaceFailedNodeRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ReplaceFailedNode) export class ReplaceFailedNodeRequest extends ReplaceFailedNodeRequestBase { public constructor( - host: ZWaveHost, - options: ReplaceFailedNodeRequestOptions, + options: ReplaceFailedNodeRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.failedNodeId = options.failedNodeId; } @@ -74,57 +76,87 @@ 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); } } +export interface ReplaceFailedNodeResponseOptions { + replaceStatus: ReplaceFailedNodeStartFlags; +} + @messageTypes(MessageType.Response, FunctionType.ReplaceFailedNode) export class ReplaceFailedNodeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ReplaceFailedNodeResponseOptions & MessageBaseOptions, ) { - super(host, options); - this._replaceStatus = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.replaceStatus = options.replaceStatus; } - private _replaceStatus: ReplaceFailedNodeStartFlags; - public get replaceStatus(): ReplaceFailedNodeStartFlags { - return this._replaceStatus; + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ReplaceFailedNodeResponse { + const replaceStatus: ReplaceFailedNodeStartFlags = raw.payload[0]; + + return new this({ + replaceStatus, + }); } + public replaceStatus: ReplaceFailedNodeStartFlags; + public isOK(): boolean { - return this._replaceStatus === ReplaceFailedNodeStartFlags.OK; + return this.replaceStatus === ReplaceFailedNodeStartFlags.OK; } } +export interface ReplaceFailedNodeRequestStatusReportOptions { + replaceStatus: ReplaceFailedNodeStatus; +} + export class ReplaceFailedNodeRequestStatusReport extends ReplaceFailedNodeRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & ReplaceFailedNodeRequestStatusReportOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); - this.callbackId = this.payload[0]; - this._replaceStatus = this.payload[1]; + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.replaceStatus = options.replaceStatus; } - private _replaceStatus: ReplaceFailedNodeStatus; - public get replaceStatus(): ReplaceFailedNodeStatus { - return this._replaceStatus; + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ReplaceFailedNodeRequestStatusReport { + const callbackId = raw.payload[0]; + const replaceStatus: ReplaceFailedNodeStatus = raw.payload[1]; + + return new this({ + callbackId, + replaceStatus, + }); } + public replaceStatus: ReplaceFailedNodeStatus; + public isOK(): boolean { return ( - this._replaceStatus + this.replaceStatus === ReplaceFailedNodeStatus.FailedNodeReplaceDone ); } diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts b/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts similarity index 61% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts rename to packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts index 088ad6e69aab..a4a26b4b4b3f 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeInfoMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RequestNodeInfoMessages.ts @@ -4,18 +4,17 @@ 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, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, expectedCallback, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -24,7 +23,7 @@ import { ApplicationUpdateRequestNodeInfoRequestFailed, } from "../application/ApplicationUpdateRequest"; -interface RequestNodeInfoResponseOptions extends MessageBaseOptions { +export interface RequestNodeInfoResponseOptions { wasSent: boolean; } @@ -33,15 +32,21 @@ export class RequestNodeInfoResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | RequestNodeInfoResponseOptions, + options: RequestNodeInfoResponseOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.wasSent = this.payload[0] !== 0; - } else { - this.wasSent = options.wasSent; - } + super(options); + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): RequestNodeInfoResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); } public wasSent: boolean; @@ -50,9 +55,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 { @@ -63,7 +68,7 @@ export class RequestNodeInfoResponse extends Message } } -interface RequestNodeInfoRequestOptions extends MessageBaseOptions { +export interface RequestNodeInfoRequestOptions { nodeId: number; } @@ -82,21 +87,27 @@ function testCallbackForRequestNodeInfoRequest( @expectedResponse(RequestNodeInfoResponse) @expectedCallback(testCallbackForRequestNodeInfoRequest) @priority(MessagePriority.NodeQuery) -export class RequestNodeInfoRequest extends Message implements INodeQuery { +export class RequestNodeInfoRequest extends Message { public constructor( - host: ZWaveHost, - options: RequestNodeInfoRequestOptions | MessageDeserializationOptions, + options: RequestNodeInfoRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.nodeId = parseNodeID( - this.payload, - this.host.nodeIdType, - 0, - ).nodeId; - } else { - this.nodeId = options.nodeId; - } + super(options); + this.nodeId = options.nodeId; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): RequestNodeInfoRequest { + const nodeId = parseNodeID( + raw.payload, + ctx.nodeIdType, + 0, + ).nodeId; + + return new this({ + nodeId, + }); } public nodeId: number; @@ -106,9 +117,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/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts similarity index 51% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts rename to packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts index 8008444c9e1a..cf980afa4cfd 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/RequestNodeNeighborUpdateMessages.ts @@ -1,16 +1,19 @@ 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, + MessageParsingContext, + MessageRaw, + MultiStageCallback, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + MessageOrigin, MessageType, expectedCallback, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -22,37 +25,35 @@ export enum NodeNeighborUpdateStatus { UpdateFailed = 0x23, } -export interface RequestNodeNeighborUpdateRequestOptions - extends MessageBaseOptions -{ - nodeId: number; - /** This must be determined with {@link computeNeighborDiscoveryTimeout} */ - discoveryTimeout: number; -} - @messageTypes(MessageType.Request, FunctionType.RequestNodeNeighborUpdate) @priority(MessagePriority.Controller) export class RequestNodeNeighborUpdateRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== RequestNodeNeighborUpdateReport - ) { - return new RequestNodeNeighborUpdateReport(host, options); + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): RequestNodeNeighborUpdateRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return RequestNodeNeighborUpdateRequest.from(raw, ctx); + } else { + return RequestNodeNeighborUpdateReport.from(raw, ctx); } - super(host, options); } } +export interface RequestNodeNeighborUpdateRequestOptions { + nodeId: number; + /** This must be determined with {@link computeNeighborDiscoveryTimeout} */ + discoveryTimeout: number; +} + @expectedCallback(FunctionType.RequestNodeNeighborUpdate) export class RequestNodeNeighborUpdateRequest extends RequestNodeNeighborUpdateRequestBase { public constructor( - host: ZWaveHost, - options: RequestNodeNeighborUpdateRequestOptions, + options: RequestNodeNeighborUpdateRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.nodeId = options.nodeId; this.discoveryTimeout = options.discoveryTimeout; } @@ -60,10 +61,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,46 +75,61 @@ export class RequestNodeNeighborUpdateRequest public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), - message: { "callback id": this.callbackId }, + message: { + "callback id": this.callbackId ?? "(not set)", + }, }; } } +export interface RequestNodeNeighborUpdateReportOptions { + updateStatus: NodeNeighborUpdateStatus; +} + export class RequestNodeNeighborUpdateReport extends RequestNodeNeighborUpdateRequestBase implements SuccessIndicator, MultiStageCallback { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: RequestNodeNeighborUpdateReportOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - this.callbackId = this.payload[0]; - this._updateStatus = this.payload[1]; + this.callbackId = options.callbackId; + this.updateStatus = options.updateStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): RequestNodeNeighborUpdateReport { + const callbackId = raw.payload[0]; + const updateStatus: NodeNeighborUpdateStatus = raw.payload[1]; + + return new this({ + callbackId, + updateStatus, + }); } isOK(): boolean { - return this._updateStatus !== NodeNeighborUpdateStatus.UpdateFailed; + return this.updateStatus !== NodeNeighborUpdateStatus.UpdateFailed; } isFinal(): boolean { - return this._updateStatus === NodeNeighborUpdateStatus.UpdateDone; + return this.updateStatus === NodeNeighborUpdateStatus.UpdateDone; } - private _updateStatus: NodeNeighborUpdateStatus; - public get updateStatus(): NodeNeighborUpdateStatus { - return this._updateStatus; - } + public updateStatus: NodeNeighborUpdateStatus; public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), message: { - "callback id": this.callbackId, + "callback id": this.callbackId ?? "(not set)", "update status": getEnumMemberName( NodeNeighborUpdateStatus, - this._updateStatus, + this.updateStatus, ), }, }; diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts b/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts similarity index 55% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts rename to packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts index 4d38e0d3b6cd..1a311f601558 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetLearnModeMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/SetLearnModeMessages.ts @@ -5,17 +5,17 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -47,18 +47,19 @@ export enum LearnModeStatus { @messageTypes(MessageType.Request, FunctionType.SetLearnMode) @priority(MessagePriority.Controller) export class SetLearnModeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== SetLearnModeCallback - ) { - return new SetLearnModeCallback(host, options); + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SetLearnModeRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SetLearnModeRequest.from(raw, ctx); + } else { + return SetLearnModeCallback.from(raw, ctx); } - super(host, options); } } -export interface SetLearnModeRequestOptions extends MessageBaseOptions { +export interface SetLearnModeRequestOptions { intent: LearnModeIntent; } @@ -66,50 +67,71 @@ 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, + options: SetLearnModeRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.intent = options.intent; - } + super(options); + this.intent = options.intent; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLearnModeRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SetLearnModeRequest({}); } 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), }, }; } } +export interface SetLearnModeResponseOptions { + success: boolean; +} + @messageTypes(MessageType.Response, FunctionType.SetLearnMode) export class SetLearnModeResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SetLearnModeResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLearnModeResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } public readonly success: boolean; @@ -126,22 +148,46 @@ export class SetLearnModeResponse extends Message implements SuccessIndicator { } } +export interface SetLearnModeCallbackOptions { + status: LearnModeStatus; + assignedNodeId: number; + statusMessage?: Buffer; +} + export class SetLearnModeCallback extends SetLearnModeRequestBase implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SetLearnModeCallbackOptions & MessageBaseOptions, ) { - super(host, options); - - this.callbackId = this.payload[0]; - this.status = this.payload[1]; - this.assignedNodeId = this.payload[2]; - if (this.payload.length > 3) { - const msgLength = this.payload[3]; - this.statusMessage = this.payload.subarray(4, 4 + msgLength); + super(options); + + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.status = options.status; + this.assignedNodeId = options.assignedNodeId; + this.statusMessage = options.statusMessage; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetLearnModeCallback { + const callbackId = raw.payload[0]; + const status: LearnModeStatus = raw.payload[1]; + const assignedNodeId = raw.payload[2]; + let statusMessage: Buffer | undefined; + if (raw.payload.length > 3) { + const msgLength = raw.payload[3]; + statusMessage = raw.payload.subarray(4, 4 + msgLength); } + + return new this({ + callbackId, + status, + assignedNodeId, + statusMessage, + }); } public readonly status: LearnModeStatus; @@ -154,7 +200,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/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts similarity index 59% rename from packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts rename to packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts index 2af0f8077e9e..de19e54e8398 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetPriorityRouteMessages.ts +++ b/packages/serial/src/serialapi/network-mgmt/SetPriorityRouteMessages.ts @@ -10,16 +10,16 @@ import { encodeNodeID, parseNodeID, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, type SuccessIndicator, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -39,50 +39,52 @@ export type SetPriorityRouteRequestOptions = @expectedResponse(FunctionType.SetPriorityRoute) export class SetPriorityRouteRequest extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | (MessageBaseOptions & SetPriorityRouteRequestOptions), + options: SetPriorityRouteRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - if (options.repeaters) { - if ( - options.repeaters.length > MAX_REPEATERS - || options.repeaters.some((id) => id < 1 || id > MAX_NODES) - ) { - throw new ZWaveError( - `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.routeSpeed == undefined) { - throw new ZWaveError( - `When setting a priority route, repeaters and route speed must be set together`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.repeaters = options.repeaters; - this.routeSpeed = options.routeSpeed; + super(options); + if (options.repeaters) { + if ( + options.repeaters.length > MAX_REPEATERS + || options.repeaters.some((id) => id < 1 || id > MAX_NODES) + ) { + throw new ZWaveError( + `The repeaters array must contain at most ${MAX_REPEATERS} node IDs between 1 and ${MAX_NODES}`, + ZWaveErrorCodes.Argument_Invalid, + ); } - - this.destinationNodeId = options.destinationNodeId; + if (options.routeSpeed == undefined) { + throw new ZWaveError( + `When setting a priority route, repeaters and route speed must be set together`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.repeaters = options.repeaters; + this.routeSpeed = options.routeSpeed; } + + this.destinationNodeId = options.destinationNodeId; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetPriorityRouteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SetPriorityRouteRequest({}); } public destinationNodeId: number; 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 +103,7 @@ export class SetPriorityRouteRequest extends Message { ]); } - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -127,23 +129,38 @@ export class SetPriorityRouteRequest extends Message { } } +export interface SetPriorityRouteResponseOptions { + success: boolean; +} + @messageTypes(MessageType.Response, FunctionType.SetPriorityRoute) export class SetPriorityRouteResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: SetPriorityRouteResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SetPriorityRouteResponse { // 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, + raw.payload, + ctx.nodeIdType, 0, ); + const success = raw.payload[bytesRead] !== 0; - this.success = this.payload[bytesRead] !== 0; + return new this({ + success, + }); } isOK(): boolean { diff --git a/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts b/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts new file mode 100644 index 000000000000..b8c92b518054 --- /dev/null +++ b/packages/serial/src/serialapi/network-mgmt/SetSUCNodeIDMessages.ts @@ -0,0 +1,186 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitOptions, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, +} from "@zwave-js/core"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; +import { + FunctionType, + Message, + type MessageBaseOptions, + MessageOrigin, + MessageType, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; + +export enum SetSUCNodeIdStatus { + Succeeded = 0x05, + Failed = 0x06, +} + +export interface SetSUCNodeIdRequestOptions { + ownNodeId: number; + sucNodeId: number; + enableSUC: boolean; + enableSIS: boolean; + transmitOptions?: TransmitOptions; +} + +@messageTypes(MessageType.Request, FunctionType.SetSUCNodeId) +@priority(MessagePriority.Controller) +export class SetSUCNodeIdRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SetSUCNodeIdRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SetSUCNodeIdRequest.from(raw, ctx); + } else { + return SetSUCNodeIdRequestStatusReport.from(raw, ctx); + } + } +} + +@expectedResponse(FunctionType.SetSUCNodeId) +@expectedCallback(FunctionType.SetSUCNodeId) +export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { + public constructor( + options: SetSUCNodeIdRequestOptions & MessageBaseOptions, + ) { + super(options); + this.sucNodeId = options.sucNodeId; + this.enableSUC = options.enableSUC; + this.enableSIS = options.enableSIS; + this.transmitOptions = options.transmitOptions + ?? TransmitOptions.DEFAULT; + this._ownNodeId = options.ownNodeId; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetSUCNodeIdRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new SetSUCNodeIdRequest({}); + } + + public sucNodeId: number; + public enableSUC: boolean; + public enableSIS: boolean; + public transmitOptions: TransmitOptions; + + private _ownNodeId: number; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.sucNodeId, ctx.nodeIdType); + this.payload = Buffer.concat([ + nodeId, + Buffer.from([ + this.enableSUC ? 0x01 : 0x00, + this.transmitOptions, + this.enableSIS ? 0x01 : 0x00, + this.callbackId, + ]), + ]); + + return super.serialize(ctx); + } + + public expectsCallback(): boolean { + if (this.sucNodeId === this._ownNodeId) return false; + return super.expectsCallback(); + } +} + +export interface SetSUCNodeIdResponseOptions { + wasExecuted: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SetSUCNodeId) +export class SetSUCNodeIdResponse extends Message implements SuccessIndicator { + public constructor( + options: SetSUCNodeIdResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.wasExecuted = options.wasExecuted; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetSUCNodeIdResponse { + const wasExecuted = raw.payload[0] !== 0; + + return new this({ + wasExecuted, + }); + } + + isOK(): boolean { + return this.wasExecuted; + } + + public wasExecuted: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was executed": this.wasExecuted }, + }; + } +} + +export interface SetSUCNodeIdRequestStatusReportOptions { + status: SetSUCNodeIdStatus; +} + +export class SetSUCNodeIdRequestStatusReport extends SetSUCNodeIdRequestBase + implements SuccessIndicator +{ + public constructor( + options: SetSUCNodeIdRequestStatusReportOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.callbackId = options.callbackId; + this.status = options.status; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SetSUCNodeIdRequestStatusReport { + const callbackId = raw.payload[0]; + const status: SetSUCNodeIdStatus = raw.payload[1]; + + return new this({ + callbackId, + status, + }); + } + + public status: SetSUCNodeIdStatus; + + public isOK(): boolean { + return this.status === SetSUCNodeIdStatus.Succeeded; + } +} diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts similarity index 50% rename from packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts rename to packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts index f9b43b6e82f6..6c6a7c382592 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongBufferMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMReadLongBufferMessages.ts @@ -4,21 +4,21 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; import { num2hex } from "@zwave-js/shared"; -export interface ExtNVMReadLongBufferRequestOptions extends MessageBaseOptions { +export interface ExtNVMReadLongBufferRequestOptions { offset: number; length: number; } @@ -28,44 +28,46 @@ export interface ExtNVMReadLongBufferRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ExtNVMReadLongBuffer) export class ExtNVMReadLongBufferRequest extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtNVMReadLongBufferRequestOptions, + options: ExtNVMReadLongBufferRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { + super(options); + if (options.offset < 0 || options.offset > 0xffffff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The offset must be a 24-bit number!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.length < 1 || options.length > 0xffff) { + throw new ZWaveError( + "The length must be between 1 and 65535", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.offset < 0 || options.offset > 0xffffff) { - throw new ZWaveError( - "The offset must be a 24-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.length < 1 || options.length > 0xffff) { - throw new ZWaveError( - "The length must be between 1 and 65535", - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.offset = options.offset; - this.length = options.length; } + + this.offset = options.offset; + this.length = options.length; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMReadLongBufferRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ExtNVMReadLongBufferRequest({}); } 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 { @@ -79,14 +81,28 @@ export class ExtNVMReadLongBufferRequest extends Message { } } +export interface ExtNVMReadLongBufferResponseOptions { + buffer: Buffer; +} + @messageTypes(MessageType.Response, FunctionType.ExtNVMReadLongBuffer) export class ExtNVMReadLongBufferResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ExtNVMReadLongBufferResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.buffer = this.payload; + super(options); + + // TODO: Check implementation: + this.buffer = options.buffer; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMReadLongBufferResponse { + return new this({ + buffer: raw.payload, + }); } public readonly buffer: Buffer; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts similarity index 50% rename from packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts rename to packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts index 245a6b0b0782..e4f731952e2b 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMReadLongByteMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMReadLongByteMessages.ts @@ -4,21 +4,21 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; import { num2hex } from "@zwave-js/shared"; -export interface ExtNVMReadLongByteRequestOptions extends MessageBaseOptions { +export interface ExtNVMReadLongByteRequestOptions { offset: number; } @@ -27,34 +27,36 @@ export interface ExtNVMReadLongByteRequestOptions extends MessageBaseOptions { @expectedResponse(FunctionType.ExtNVMReadLongByte) export class ExtNVMReadLongByteRequest extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtNVMReadLongByteRequestOptions, + options: ExtNVMReadLongByteRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { + super(options); + if (options.offset < 0 || options.offset > 0xffffff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The offset must be a 24-bit number!", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.offset < 0 || options.offset > 0xffffff) { - throw new ZWaveError( - "The offset must be a 24-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.offset = options.offset; } + this.offset = options.offset; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMReadLongByteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ExtNVMReadLongByteRequest({}); } 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 { @@ -65,14 +67,30 @@ export class ExtNVMReadLongByteRequest extends Message { } } +export interface ExtNVMReadLongByteResponseOptions { + byte: number; +} + @messageTypes(MessageType.Response, FunctionType.ExtNVMReadLongByte) export class ExtNVMReadLongByteResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ExtNVMReadLongByteResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.byte = this.payload[0]; + super(options); + + // TODO: Check implementation: + this.byte = options.byte; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMReadLongByteResponse { + const byte = raw.payload[0]; + + return new this({ + byte, + }); } public readonly byte: number; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts similarity index 51% rename from packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts rename to packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts index ba4f9e47433b..16ec55c2fc32 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongBufferMessages.ts @@ -4,23 +4,21 @@ import { ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; import { num2hex } from "@zwave-js/shared"; -export interface ExtNVMWriteLongBufferRequestOptions - extends MessageBaseOptions -{ +export interface ExtNVMWriteLongBufferRequestOptions { offset: number; buffer: Buffer; } @@ -30,44 +28,46 @@ export interface ExtNVMWriteLongBufferRequestOptions @expectedResponse(FunctionType.ExtNVMWriteLongBuffer) export class ExtNVMWriteLongBufferRequest extends Message { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtNVMWriteLongBufferRequestOptions, + options: ExtNVMWriteLongBufferRequestOptions & MessageBaseOptions, ) { - super(host, options); - if (gotDeserializationOptions(options)) { + super(options); + if (options.offset < 0 || options.offset > 0xffffff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The offset must be a 24-bit number!", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.offset < 0 || options.offset > 0xffffff) { - throw new ZWaveError( - "The offset must be a 24-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.buffer.length < 1 || options.buffer.length > 0xffff) { - throw new ZWaveError( - "The buffer must be between 1 and 65535 bytes long", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.offset = options.offset; - this.buffer = options.buffer; } + if (options.buffer.length < 1 || options.buffer.length > 0xffff) { + throw new ZWaveError( + "The buffer must be between 1 and 65535 bytes long", + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.offset = options.offset; + this.buffer = options.buffer; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMWriteLongBufferRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ExtNVMWriteLongBufferRequest({}); } 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 { @@ -83,14 +83,30 @@ export class ExtNVMWriteLongBufferRequest extends Message { } } +export interface ExtNVMWriteLongBufferResponseOptions { + success: boolean; +} + @messageTypes(MessageType.Response, FunctionType.ExtNVMWriteLongBuffer) export class ExtNVMWriteLongBufferResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ExtNVMWriteLongBufferResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.success = this.payload[0] !== 0; + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMWriteLongBufferResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); } public readonly success: boolean; diff --git a/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts new file mode 100644 index 000000000000..f37424913f10 --- /dev/null +++ b/packages/serial/src/serialapi/nvm/ExtNVMWriteLongByteMessages.ts @@ -0,0 +1,123 @@ +import { + type MessageOrCCLogEntry, + MessagePriority, + ZWaveError, + ZWaveErrorCodes, +} from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, + MessageType, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { num2hex } from "@zwave-js/shared"; + +export interface ExtNVMWriteLongByteRequestOptions { + offset: number; + byte: number; +} + +@messageTypes(MessageType.Request, FunctionType.ExtExtWriteLongByte) +@priority(MessagePriority.Controller) +@expectedResponse(FunctionType.ExtExtWriteLongByte) +export class ExtNVMWriteLongByteRequest extends Message { + public constructor( + options: ExtNVMWriteLongByteRequestOptions & MessageBaseOptions, + ) { + super(options); + if (options.offset < 0 || options.offset > 0xffffff) { + throw new ZWaveError( + "The offset must be a 24-bit number!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + if ((options.byte & 0xff) !== options.byte) { + throw new ZWaveError( + "The data must be a byte!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.offset = options.offset; + this.byte = options.byte; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMWriteLongByteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new ExtNVMWriteLongByteRequest({}); + } + + public offset: number; + public byte: number; + + 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(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + offset: num2hex(this.offset), + byte: num2hex(this.byte), + }, + }; + } +} + +export interface ExtNVMWriteLongByteResponseOptions { + success: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.ExtExtWriteLongByte) +export class ExtNVMWriteLongByteResponse extends Message { + public constructor( + options: ExtNVMWriteLongByteResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.success = options.success; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtNVMWriteLongByteResponse { + const success = raw.payload[0] !== 0; + + return new this({ + success, + }); + } + + public readonly success: boolean; + + public isOK(): boolean { + return this.success; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + success: this.success, + }, + }; + } +} diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts b/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts similarity index 58% rename from packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts rename to packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts index e8fac7aa425c..3b7ef415d22f 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtendedNVMOperationsMessages.ts +++ b/packages/serial/src/serialapi/nvm/ExtendedNVMOperationsMessages.ts @@ -6,17 +6,18 @@ import { ZWaveErrorCodes, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { SuccessIndicator } from "@zwave-js/serial"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; import { FunctionType, Message, type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -45,13 +46,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 +74,8 @@ export class ExtendedNVMOperationsRequest extends Message { export class ExtendedNVMOperationsOpenRequest extends ExtendedNVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options: MessageBaseOptions = {}) { + super(options); this.command = ExtendedNVMOperationsCommand.Open; } } @@ -84,17 +85,15 @@ export class ExtendedNVMOperationsOpenRequest export class ExtendedNVMOperationsCloseRequest extends ExtendedNVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options: MessageBaseOptions = {}) { + super(options); this.command = ExtendedNVMOperationsCommand.Close; } } // ============================================================================= -export interface ExtendedNVMOperationsReadRequestOptions - extends MessageBaseOptions -{ +export interface ExtendedNVMOperationsReadRequestOptions { length: number; offset: number; } @@ -103,47 +102,49 @@ export class ExtendedNVMOperationsReadRequest extends ExtendedNVMOperationsRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtendedNVMOperationsReadRequestOptions, + options: ExtendedNVMOperationsReadRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.command = ExtendedNVMOperationsCommand.Read; - if (gotDeserializationOptions(options)) { + if (options.length < 0 || options.length > 0xff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The length must be between 0 and 255!", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.length < 0 || options.length > 0xff) { - throw new ZWaveError( - "The length must be between 0 and 255!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.offset < 0 || options.offset > 0xffffffff) { - throw new ZWaveError( - "The offset must be a 32-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.length = options.length; - this.offset = options.offset; } + if (options.offset < 0 || options.offset > 0xffffffff) { + throw new ZWaveError( + "The offset must be a 32-bit number!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + + this.length = options.length; + this.offset = options.offset; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtendedNVMOperationsReadRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new 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 { @@ -161,9 +162,7 @@ export class ExtendedNVMOperationsReadRequest // ============================================================================= -export interface ExtendedNVMOperationsWriteRequestOptions - extends MessageBaseOptions -{ +export interface ExtendedNVMOperationsWriteRequestOptions { offset: number; buffer: Buffer; } @@ -172,47 +171,49 @@ export class ExtendedNVMOperationsWriteRequest extends ExtendedNVMOperationsRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtendedNVMOperationsWriteRequestOptions, + options: ExtendedNVMOperationsWriteRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.command = ExtendedNVMOperationsCommand.Write; - if (gotDeserializationOptions(options)) { + if (options.offset < 0 || options.offset > 0xffffffff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The offset must be a 32-bit number!", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.offset < 0 || options.offset > 0xffffffff) { - throw new ZWaveError( - "The offset must be a 32-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.buffer.length < 1 || options.buffer.length > 0xff) { - throw new ZWaveError( - "The buffer must be between 1 and 255 bytes long", - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.offset = options.offset; - this.buffer = options.buffer; } + if (options.buffer.length < 1 || options.buffer.length > 0xff) { + throw new ZWaveError( + "The buffer must be between 1 and 255 bytes long", + ZWaveErrorCodes.Argument_Invalid, + ); + } + + this.offset = options.offset; + this.buffer = options.buffer; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtendedNVMOperationsWriteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new 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 { @@ -231,43 +232,60 @@ export class ExtendedNVMOperationsWriteRequest } // ============================================================================= +export interface ExtendedNVMOperationsResponseOptions { + status: ExtendedNVMOperationStatus; + offsetOrSize: number; + bufferOrBitmask: Buffer; +} @messageTypes(MessageType.Response, FunctionType.ExtendedNVMOperations) export class ExtendedNVMOperationsResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: ExtendedNVMOperationsResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); - this.status = this.payload[0]; - const dataLength = this.payload[1]; + // TODO: Check implementation: + this.status = options.status; + this.offsetOrSize = options.offsetOrSize; + this.bufferOrBitmask = options.bufferOrBitmask; + } + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): ExtendedNVMOperationsResponse { + validatePayload(raw.payload.length >= 2); + const status: ExtendedNVMOperationStatus = raw.payload[0]; + const dataLength = raw.payload[1]; let offset = 2; - - if (this.payload.length >= offset + 4) { - this.offsetOrSize = this.payload.readUInt32BE(offset); - } else { - this.offsetOrSize = 0; + let offsetOrSize = 0; + if (raw.payload.length >= offset + 4) { + offsetOrSize = raw.payload.readUInt32BE(offset); } offset += 4; - // The buffer will contain: // - Read command: the read NVM data // - Write/Close command: nothing // - Open command: bit mask of supported sub-commands - if (dataLength > 0 && this.payload.length >= offset + dataLength) { - this.bufferOrBitmask = this.payload.subarray( + let bufferOrBitmask: Buffer; + if (dataLength > 0 && raw.payload.length >= offset + dataLength) { + bufferOrBitmask = raw.payload.subarray( offset, offset + dataLength, ); } else { - this.bufferOrBitmask = Buffer.from([]); + bufferOrBitmask = Buffer.from([]); } + + return new this({ + status, + offsetOrSize, + bufferOrBitmask, + }); } isOK(): boolean { diff --git a/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts b/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts similarity index 57% rename from packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts rename to packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts index 0c1d229373bd..ffc021cd4393 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/FirmwareUpdateNVMMessages.ts +++ b/packages/serial/src/serialapi/nvm/FirmwareUpdateNVMMessages.ts @@ -2,24 +2,21 @@ import { type MessageOrCCLogEntry, MessagePriority, type MessageRecord, - ZWaveError, - ZWaveErrorCodes, createSimpleReflectionDecorator, validatePayload, } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import type { - DeserializingMessageConstructor, MessageBaseOptions, + MessageConstructor, + MessageEncodingContext, + MessageParsingContext, + MessageRaw, } from "@zwave-js/serial"; import { FunctionType, Message, - type MessageDeserializationOptions, - type MessageOptions, MessageType, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -37,12 +34,12 @@ export enum FirmwareUpdateNVMCommand { // We need to define the decorators for Requests and Responses separately const { decorator: subCommandRequest, - // lookupConstructor: getSubCommandRequestConstructor, + lookupConstructor: getSubCommandRequestConstructor, lookupValue: getSubCommandForRequest, } = createSimpleReflectionDecorator< FirmwareUpdateNVMRequest, [command: FirmwareUpdateNVMCommand], - DeserializingMessageConstructor + MessageConstructor >({ name: "subCommandRequest", }); @@ -50,10 +47,11 @@ const { const { decorator: subCommandResponse, lookupConstructor: getSubCommandResponseConstructor, + lookupValue: getSubCommandForResponse, } = createSimpleReflectionDecorator< FirmwareUpdateNVMResponse, [command: FirmwareUpdateNVMCommand], - DeserializingMessageConstructor + MessageConstructor >({ name: "subCommandResponse", }); @@ -66,31 +64,54 @@ function testResponseForFirmwareUpdateNVMRequest( return (sent as FirmwareUpdateNVMRequest).command === received.command; } +export interface FirmwareUpdateNVMRequestOptions { + command?: FirmwareUpdateNVMCommand; +} + @messageTypes(MessageType.Request, FunctionType.FirmwareUpdateNVM) @priority(MessagePriority.Controller) @expectedResponse(testResponseForFirmwareUpdateNVMRequest) export class FirmwareUpdateNVMRequest extends Message { - public constructor(host: ZWaveHost, options: MessageOptions = {}) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.command = getSubCommandForRequest(this)!; + public constructor( + options: FirmwareUpdateNVMRequestOptions & MessageBaseOptions = {}, + ) { + super(options); + this.command = options.command ?? getSubCommandForRequest(this)!; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): FirmwareUpdateNVMRequest { + const command: FirmwareUpdateNVMCommand = raw.payload[0]; + const payload = raw.payload.subarray(1); + + const CommandConstructor = getSubCommandRequestConstructor( + command, + ); + if (CommandConstructor) { + return CommandConstructor.from( + raw.withPayload(payload), + ctx, + ) as FirmwareUpdateNVMRequest; } + + const ret = new FirmwareUpdateNVMRequest({ + command, + }); + ret.payload = payload; + return ret; } 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 { @@ -107,23 +128,41 @@ export class FirmwareUpdateNVMRequest extends Message { } } +export interface FirmwareUpdateNVMResponseOptions { + command?: FirmwareUpdateNVMCommand; +} + @messageTypes(MessageType.Response, FunctionType.FirmwareUpdateNVM) export class FirmwareUpdateNVMResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: FirmwareUpdateNVMResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.command = this.payload[0]; + super(options); + this.command = options.command ?? getSubCommandForResponse(this)!; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): FirmwareUpdateNVMResponse { + const command: FirmwareUpdateNVMCommand = raw.payload[0]; + const payload = raw.payload.subarray(1); const CommandConstructor = getSubCommandResponseConstructor( - this.command, + command, ); - if (CommandConstructor && (new.target as any) !== CommandConstructor) { - return new CommandConstructor(host, options); + if (CommandConstructor) { + return CommandConstructor.from( + raw.withPayload(payload), + ctx, + ) as FirmwareUpdateNVMResponse; } - this.payload = this.payload.subarray(1); + const ret = new FirmwareUpdateNVMResponse({ + command, + }); + ret.payload = payload; + return ret; } public command: FirmwareUpdateNVMCommand; @@ -147,14 +186,29 @@ export class FirmwareUpdateNVMResponse extends Message { @subCommandRequest(FirmwareUpdateNVMCommand.Init) export class FirmwareUpdateNVM_InitRequest extends FirmwareUpdateNVMRequest {} +export interface FirmwareUpdateNVM_InitResponseOptions { + supported: boolean; +} + @subCommandResponse(FirmwareUpdateNVMCommand.Init) export class FirmwareUpdateNVM_InitResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: FirmwareUpdateNVM_InitResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.supported = this.payload[0] !== 0; + super(options); + + this.supported = options.supported; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_InitResponse { + const supported = raw.payload[0] !== 0; + + return new this({ + supported, + }); } public readonly supported: boolean; @@ -170,9 +224,7 @@ export class FirmwareUpdateNVM_InitResponse extends FirmwareUpdateNVMResponse { // ============================================================================= -export interface FirmwareUpdateNVM_SetNewImageRequestOptions - extends MessageBaseOptions -{ +export interface FirmwareUpdateNVM_SetNewImageRequestOptions { newImage: boolean; } @@ -181,30 +233,32 @@ export class FirmwareUpdateNVM_SetNewImageRequest extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | FirmwareUpdateNVM_SetNewImageRequestOptions, + & FirmwareUpdateNVM_SetNewImageRequestOptions + & MessageBaseOptions, ) { - super(host, options); - this.command = FirmwareUpdateNVMCommand.SetNewImage; - - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.newImage = options.newImage; - } + super(options); + + this.newImage = options.newImage; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_SetNewImageRequest { + const newImage: boolean = raw.payload[0] !== 0; + + return new this({ + newImage, + }); } 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 { @@ -216,16 +270,33 @@ export class FirmwareUpdateNVM_SetNewImageRequest } } +export interface FirmwareUpdateNVM_SetNewImageResponseOptions { + changed: boolean; +} + @subCommandResponse(FirmwareUpdateNVMCommand.SetNewImage) export class FirmwareUpdateNVM_SetNewImageResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & FirmwareUpdateNVM_SetNewImageResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.changed = this.payload[0] !== 0; + super(options); + + this.changed = options.changed; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_SetNewImageResponse { + const changed = raw.payload[0] !== 0; + + return new this({ + changed, + }); } public readonly changed: boolean; @@ -246,16 +317,33 @@ export class FirmwareUpdateNVM_GetNewImageRequest extends FirmwareUpdateNVMRequest {} +export interface FirmwareUpdateNVM_GetNewImageResponseOptions { + newImage: boolean; +} + @subCommandResponse(FirmwareUpdateNVMCommand.GetNewImage) export class FirmwareUpdateNVM_GetNewImageResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & FirmwareUpdateNVM_GetNewImageResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.newImage = this.payload[0] !== 0; + super(options); + + this.newImage = options.newImage; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_GetNewImageResponse { + const newImage: boolean = raw.payload[0] !== 0; + + return new this({ + newImage, + }); } public readonly newImage: boolean; @@ -271,9 +359,7 @@ export class FirmwareUpdateNVM_GetNewImageResponse // ============================================================================= -export interface FirmwareUpdateNVM_UpdateCRC16RequestOptions - extends MessageBaseOptions -{ +export interface FirmwareUpdateNVM_UpdateCRC16RequestOptions { crcSeed: number; offset: number; blockLength: number; @@ -284,24 +370,31 @@ export class FirmwareUpdateNVM_UpdateCRC16Request extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, options: - | MessageDeserializationOptions - | FirmwareUpdateNVM_UpdateCRC16RequestOptions, + & FirmwareUpdateNVM_UpdateCRC16RequestOptions + & MessageBaseOptions, ) { - super(host, options); + super(options); this.command = FirmwareUpdateNVMCommand.UpdateCRC16; - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.crcSeed = options.crcSeed; - this.offset = options.offset; - this.blockLength = options.blockLength; - } + this.crcSeed = options.crcSeed; + this.offset = options.offset; + this.blockLength = options.blockLength; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_UpdateCRC16Request { + const offset = raw.payload.readUIntBE(0, 3); + const blockLength = raw.payload.readUInt16BE(3); + const crcSeed = raw.payload.readUInt16BE(5); + + return new this({ + crcSeed, + offset, + blockLength, + }); } public crcSeed: number; @@ -313,13 +406,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 { @@ -333,17 +426,34 @@ export class FirmwareUpdateNVM_UpdateCRC16Request } } +export interface FirmwareUpdateNVM_UpdateCRC16ResponseOptions { + crc16: number; +} + @subCommandResponse(FirmwareUpdateNVMCommand.UpdateCRC16) export class FirmwareUpdateNVM_UpdateCRC16Response extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & FirmwareUpdateNVM_UpdateCRC16ResponseOptions + & MessageBaseOptions, ) { - super(host, options); - validatePayload(this.payload.length >= 2); - this.crc16 = this.payload.readUint16BE(0); + super(options); + + this.crc16 = options.crc16; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_UpdateCRC16Response { + validatePayload(raw.payload.length >= 2); + const crc16 = raw.payload.readUInt16BE(0); + + return new this({ + crc16, + }); } public readonly crc16: number; @@ -369,17 +479,34 @@ export class FirmwareUpdateNVM_IsValidCRC16Request } } +export interface FirmwareUpdateNVM_IsValidCRC16ResponseOptions { + isValid: boolean; +} + @subCommandResponse(FirmwareUpdateNVMCommand.IsValidCRC16) export class FirmwareUpdateNVM_IsValidCRC16Response extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: + & FirmwareUpdateNVM_IsValidCRC16ResponseOptions + & MessageBaseOptions, ) { - super(host, options); - this.isValid = this.payload[0] !== 0; + super(options); + + this.isValid = options.isValid; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_IsValidCRC16Response { + const isValid = raw.payload[0] !== 0; // There are two more bytes containing the CRC result, but we don't care about that + + return new this({ + isValid, + }); } public readonly isValid: boolean; @@ -395,9 +522,7 @@ export class FirmwareUpdateNVM_IsValidCRC16Response // ============================================================================= -export interface FirmwareUpdateNVM_WriteRequestOptions - extends MessageBaseOptions -{ +export interface FirmwareUpdateNVM_WriteRequestOptions { offset: number; buffer: Buffer; } @@ -405,34 +530,37 @@ export interface FirmwareUpdateNVM_WriteRequestOptions @subCommandRequest(FirmwareUpdateNVMCommand.Write) export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | FirmwareUpdateNVM_WriteRequestOptions, + options: FirmwareUpdateNVM_WriteRequestOptions & MessageBaseOptions, ) { - super(host, options); - this.command = FirmwareUpdateNVMCommand.Write; - - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.offset = options.offset; - this.buffer = options.buffer; - } + super(options); + + this.offset = options.offset; + this.buffer = options.buffer; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_WriteRequest { + const offset = raw.payload.readUIntBE(0, 3); + const bufferLength = raw.payload.readUInt16BE(3); + const buffer = raw.payload.subarray(5, 5 + bufferLength); + + return new this({ + offset, + buffer, + }); } 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.writeUIntBE(this.offset, 0, 3); this.payload.writeUInt16BE(this.buffer.length, 3); - return super.serialize(); + return super.serialize(ctx); } public toLogEntry(): MessageOrCCLogEntry { @@ -449,14 +577,29 @@ export class FirmwareUpdateNVM_WriteRequest extends FirmwareUpdateNVMRequest { } } +export interface FirmwareUpdateNVM_WriteResponseOptions { + overwritten: boolean; +} + @subCommandResponse(FirmwareUpdateNVMCommand.Write) export class FirmwareUpdateNVM_WriteResponse extends FirmwareUpdateNVMResponse { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: FirmwareUpdateNVM_WriteResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.overwritten = this.payload[0] !== 0; + super(options); + + this.overwritten = options.overwritten; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): FirmwareUpdateNVM_WriteResponse { + const overwritten = raw.payload[0] !== 0; + + return new this({ + overwritten, + }); } public readonly overwritten: boolean; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts b/packages/serial/src/serialapi/nvm/GetNVMIdMessages.ts similarity index 73% rename from packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts rename to packages/serial/src/serialapi/nvm/GetNVMIdMessages.ts index 8dfa52d06847..928e79fd10b1 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/GetNVMIdMessages.ts +++ b/packages/serial/src/serialapi/nvm/GetNVMIdMessages.ts @@ -1,9 +1,10 @@ import { type MessageOrCCLogEntry, MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; import { FunctionType, Message, - type MessageDeserializationOptions, + type MessageBaseOptions, + type MessageParsingContext, + type MessageRaw, MessageType, expectedResponse, messageTypes, @@ -71,16 +72,38 @@ export type NVMId = Pick< @priority(MessagePriority.Controller) export class GetNVMIdRequest extends Message {} +export interface GetNVMIdResponseOptions { + nvmManufacturerId: number; + memoryType: NVMType; + memorySize: NVMSize; +} + @messageTypes(MessageType.Response, FunctionType.GetNVMId) export class GetNVMIdResponse extends Message { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: GetNVMIdResponseOptions & MessageBaseOptions, ) { - super(host, options); - this.nvmManufacturerId = this.payload[1]; - this.memoryType = this.payload[2]; - this.memorySize = this.payload[3]; + super(options); + + // TODO: Check implementation: + this.nvmManufacturerId = options.nvmManufacturerId; + this.memoryType = options.memoryType; + this.memorySize = options.memorySize; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): GetNVMIdResponse { + const nvmManufacturerId = raw.payload[1]; + const memoryType: NVMType = raw.payload[2]; + const memorySize: NVMSize = raw.payload[3]; + + return new this({ + nvmManufacturerId, + memoryType, + memorySize, + }); } public readonly nvmManufacturerId: number; diff --git a/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts b/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts similarity index 57% rename from packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts rename to packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts index 5adc036411f9..0f9908669b09 100644 --- a/packages/zwave-js/src/lib/serialapi/nvm/NVMOperationsMessages.ts +++ b/packages/serial/src/serialapi/nvm/NVMOperationsMessages.ts @@ -6,17 +6,16 @@ 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 MessageOptions, + type MessageEncodingContext, + type MessageParsingContext, + type MessageRaw, MessageType, + type SuccessIndicator, expectedResponse, - gotDeserializationOptions, messageTypes, priority, } from "@zwave-js/serial"; @@ -44,13 +43,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 +66,8 @@ export class NVMOperationsRequest extends Message { // ============================================================================= export class NVMOperationsOpenRequest extends NVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options: MessageBaseOptions = {}) { + super(options); this.command = NVMOperationsCommand.Open; } } @@ -76,62 +75,64 @@ export class NVMOperationsOpenRequest extends NVMOperationsRequest { // ============================================================================= export class NVMOperationsCloseRequest extends NVMOperationsRequest { - public constructor(host: ZWaveHost, options?: MessageOptions) { - super(host, options); + public constructor(options: MessageBaseOptions = {}) { + super(options); this.command = NVMOperationsCommand.Close; } } // ============================================================================= -export interface NVMOperationsReadRequestOptions extends MessageBaseOptions { +export interface NVMOperationsReadRequestOptions { length: number; offset: number; } export class NVMOperationsReadRequest extends NVMOperationsRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | NVMOperationsReadRequestOptions, + options: NVMOperationsReadRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.command = NVMOperationsCommand.Read; - if (gotDeserializationOptions(options)) { + if (options.length < 0 || options.length > 0xff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The length must be between 0 and 255!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.offset < 0 || options.offset > 0xffff) { + throw new ZWaveError( + "The offset must be a 16-bit number!", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.length < 0 || options.length > 0xff) { - throw new ZWaveError( - "The length must be between 0 and 255!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.offset < 0 || options.offset > 0xffff) { - throw new ZWaveError( - "The offset must be a 16-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.length = options.length; - this.offset = options.offset; } + + this.length = options.length; + this.offset = options.offset; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): NVMOperationsReadRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new NVMOperationsReadRequest({}); } 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 { @@ -149,54 +150,56 @@ export class NVMOperationsReadRequest extends NVMOperationsRequest { // ============================================================================= -export interface NVMOperationsWriteRequestOptions extends MessageBaseOptions { +export interface NVMOperationsWriteRequestOptions { offset: number; buffer: Buffer; } export class NVMOperationsWriteRequest extends NVMOperationsRequest { public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | NVMOperationsWriteRequestOptions, + options: NVMOperationsWriteRequestOptions & MessageBaseOptions, ) { - super(host, options); + super(options); this.command = NVMOperationsCommand.Write; - if (gotDeserializationOptions(options)) { + if (options.offset < 0 || options.offset > 0xffff) { throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, + "The offset must be a 16-bit number!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + if (options.buffer.length < 1 || options.buffer.length > 0xff) { + throw new ZWaveError( + "The buffer must be between 1 and 255 bytes long", + ZWaveErrorCodes.Argument_Invalid, ); - } else { - if (options.offset < 0 || options.offset > 0xffff) { - throw new ZWaveError( - "The offset must be a 16-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if (options.buffer.length < 1 || options.buffer.length > 0xff) { - throw new ZWaveError( - "The buffer must be between 1 and 255 bytes long", - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.offset = options.offset; - this.buffer = options.buffer; } + + this.offset = options.offset; + this.buffer = options.buffer; + } + + public static from( + _raw: MessageRaw, + _ctx: MessageParsingContext, + ): NVMOperationsWriteRequest { + throw new ZWaveError( + `${this.name}: deserialization not implemented`, + ZWaveErrorCodes.Deserialization_NotImplemented, + ); + + // return new NVMOperationsWriteRequest({}); } 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 { @@ -215,31 +218,52 @@ export class NVMOperationsWriteRequest extends NVMOperationsRequest { } // ============================================================================= +export interface NVMOperationsResponseOptions { + status: NVMOperationStatus; + offsetOrSize: number; + buffer: Buffer; +} @messageTypes(MessageType.Response, FunctionType.NVMOperations) export class NVMOperationsResponse extends Message implements SuccessIndicator { public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, + options: NVMOperationsResponseOptions & MessageBaseOptions, ) { - super(host, options); + super(options); - validatePayload(this.payload.length >= 2); - this.status = this.payload[0]; + // TODO: Check implementation: + this.status = options.status; + this.offsetOrSize = options.offsetOrSize; + this.buffer = options.buffer; + } - if (this.payload.length >= 4) { - this.offsetOrSize = this.payload.readUInt16BE(2); + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): NVMOperationsResponse { + validatePayload(raw.payload.length >= 2); + const status: NVMOperationStatus = raw.payload[0]; + let offsetOrSize; + if (raw.payload.length >= 4) { + offsetOrSize = raw.payload.readUInt16BE(2); } else { - this.offsetOrSize = 0; + offsetOrSize = 0; } - const dataLength = this.payload[1]; + const dataLength = raw.payload[1]; // The response to the write command contains the offset and written data length, but no data - if (dataLength > 0 && this.payload.length >= 4 + dataLength) { - this.buffer = this.payload.subarray(4, 4 + dataLength); + let buffer: Buffer; + if (dataLength > 0 && raw.payload.length >= 4 + dataLength) { + buffer = raw.payload.subarray(4, 4 + dataLength); } else { - this.buffer = Buffer.from([]); + buffer = Buffer.from([]); } + + return new this({ + status, + offsetOrSize, + buffer, + }); } isOK(): boolean { diff --git a/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts new file mode 100644 index 000000000000..7d3c78a68a60 --- /dev/null +++ b/packages/serial/src/serialapi/transport/SendDataBridgeMessages.ts @@ -0,0 +1,569 @@ +import type { CommandClass } from "@zwave-js/cc"; +import { + MAX_NODES, + type MessageOrCCLogEntry, + MessagePriority, + type MulticastCC, + type MulticastDestination, + type SinglecastCC, + type TXReport, + TransmitOptions, + TransmitStatus, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, +} from "@zwave-js/core"; +import type { CCEncodingContext } from "@zwave-js/host"; +import type { + MessageEncodingContext, + MessageParsingContext, + MessageRaw, + SuccessIndicator, +} from "@zwave-js/serial"; +import { + FunctionType, + Message, + type MessageBaseOptions, + MessageOrigin, + MessageType, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { clamp } from "alcalzone-shared/math"; +import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; +import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; +import { type MessageWithCC, containsCC } from "../utils"; +import { MAX_SEND_ATTEMPTS } from "./SendDataMessages"; +import { parseTXReport, txReportToMessageRecord } from "./SendDataShared"; + +@messageTypes(MessageType.Request, FunctionType.SendDataBridge) +@priority(MessagePriority.Normal) +export class SendDataBridgeRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataBridgeRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SendDataBridgeRequest.from(raw, ctx); + } else { + return SendDataBridgeRequestTransmitReport.from(raw, ctx); + } + } +} + +export type SendDataBridgeRequestOptions< + CCType extends CommandClass = CommandClass, +> = + & ( + | { command: CCType } + | { + nodeId: number; + serializedCC: Buffer; + } + ) + & { + sourceNodeId: number; + transmitOptions?: TransmitOptions; + maxSendAttempts?: number; + }; + +@expectedResponse(FunctionType.SendDataBridge) +@expectedCallback(FunctionType.SendDataBridge) +export class SendDataBridgeRequest + extends SendDataBridgeRequestBase + implements MessageWithCC +{ + public constructor( + options: SendDataBridgeRequestOptions & MessageBaseOptions, + ) { + super(options); + + if ("command" in options) { + if ( + !options.command.isSinglecast() + && !options.command.isBroadcast() + ) { + throw new ZWaveError( + `SendDataBridgeRequest can only be used for singlecast and broadcast CCs`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.command = options.command; + this._nodeId = options.command.nodeId; + } else { + this._nodeId = options.nodeId; + this.serializedCC = options.serializedCC; + } + + this.sourceNodeId = options.sourceNodeId; + + this.transmitOptions = options.transmitOptions + ?? TransmitOptions.DEFAULT; + if (options.maxSendAttempts != undefined) { + this.maxSendAttempts = options.maxSendAttempts; + } + } + + /** Which Node ID this command originates from */ + public sourceNodeId: number; + + /** The command this message contains */ + public command: SinglecastCC | undefined; + /** Options regarding the transmission of the message */ + public transmitOptions: TransmitOptions; + + private _maxSendAttempts: number = 1; + /** The number of times the driver may try to send this message */ + public get maxSendAttempts(): number { + return this._maxSendAttempts; + } + public set maxSendAttempts(value: number) { + this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); + } + + private _nodeId: number; + public override getNodeId(): number | undefined { + return this.command?.nodeId ?? this._nodeId; + } + + public serializedCC: Buffer | undefined; + public serializeCC(ctx: CCEncodingContext): Buffer { + if (!this.serializedCC) { + if (!this.command) { + throw new ZWaveError( + `Cannot serialize a ${this.constructor.name} without a command`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.serializedCC = this.command.serialize(ctx); + } + return this.serializedCC; + } + + public prepareRetransmission(): void { + this.command?.prepareRetransmission(); + this.serializedCC = undefined; + this.callbackId = undefined; + } + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const sourceNodeId = encodeNodeID( + this.sourceNodeId, + ctx.nodeIdType, + ); + const destinationNodeId = encodeNodeID( + this.command?.nodeId ?? this._nodeId, + ctx.nodeIdType, + ); + const serializedCC = this.serializeCC(ctx); + + this.payload = Buffer.concat([ + sourceNodeId, + destinationNodeId, + Buffer.from([serializedCC.length]), + serializedCC, + Buffer.from([this.transmitOptions, 0, 0, 0, 0, this.callbackId]), + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "source node id": this.sourceNodeId, + "transmit options": num2hex(this.transmitOptions), + "callback id": this.callbackId ?? "(not set)", + }, + }; + } + + public expectsNodeUpdate(): boolean { + return ( + // We can only answer this if the command is known + this.command != undefined + // Only true singlecast commands may expect a response + && this.command.isSinglecast() + // ... and only if the command expects a response + && this.command.expectsCCResponse() + ); + } + + public isExpectedNodeUpdate(msg: Message): boolean { + return ( + // We can only answer this if the command is known + this.command != undefined + && (msg instanceof ApplicationCommandRequest + || msg instanceof BridgeApplicationCommandRequest) + && containsCC(msg) + && this.command.isExpectedCCResponse(msg.command) + ); + } +} + +export interface SendDataBridgeRequestTransmitReportOptions { + transmitStatus: TransmitStatus; + txReport?: TXReport; +} + +export class SendDataBridgeRequestTransmitReport + extends SendDataBridgeRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & SendDataBridgeRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + this.txReport = options.txReport; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataBridgeRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + // TODO: Consider NOT parsing this for transmit status other than OK or NoACK + const txReport = parseTXReport( + transmitStatus !== TransmitStatus.NoAck, + raw.payload.subarray(2), + ); + + return new this({ + callbackId, + transmitStatus, + txReport, + }); + } + + public readonly transmitStatus: TransmitStatus; + public readonly txReport: TXReport | undefined; + + public isOK(): boolean { + return this.transmitStatus === TransmitStatus.OK; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": + getEnumMemberName(TransmitStatus, this.transmitStatus) + + (this.txReport + ? `, took ${this.txReport.txTicks * 10} ms` + : ""), + ...(this.txReport + ? txReportToMessageRecord(this.txReport) + : {}), + }, + }; + } +} + +export interface SendDataBridgeResponseOptions { + wasSent: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SendDataBridge) +export class SendDataBridgeResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: SendDataBridgeResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataBridgeResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); + } + + isOK(): boolean { + return this.wasSent; + } + + public wasSent: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was sent": this.wasSent }, + }; + } +} + +@messageTypes(MessageType.Request, FunctionType.SendDataMulticastBridge) +@priority(MessagePriority.Normal) +export class SendDataMulticastBridgeRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataMulticastBridgeRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SendDataMulticastBridgeRequest.from(raw, ctx); + } else { + return SendDataMulticastBridgeRequestTransmitReport.from(raw, ctx); + } + } +} + +export type SendDataMulticastBridgeRequestOptions< + CCType extends CommandClass, +> = + & ( + | { command: CCType } + | { + nodeIds: MulticastDestination; + serializedCC: Buffer; + } + ) + & { + sourceNodeId: number; + transmitOptions?: TransmitOptions; + maxSendAttempts?: number; + }; + +@expectedResponse(FunctionType.SendDataMulticastBridge) +@expectedCallback(FunctionType.SendDataMulticastBridge) +export class SendDataMulticastBridgeRequest< + CCType extends CommandClass = CommandClass, +> extends SendDataMulticastBridgeRequestBase implements MessageWithCC { + public constructor( + options: + & SendDataMulticastBridgeRequestOptions + & MessageBaseOptions, + ) { + super(options); + + if ("command" in options) { + if (!options.command.isMulticast()) { + throw new ZWaveError( + `SendDataMulticastBridgeRequest can only be used for multicast CCs`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (options.command.nodeId.length === 0) { + throw new ZWaveError( + `At least one node must be targeted`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if ( + options.command.nodeId.some((n) => n < 1 || n > MAX_NODES) + ) { + throw new ZWaveError( + `All node IDs must be between 1 and ${MAX_NODES}!`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + this.command = options.command; + this.nodeIds = this.command.nodeId; + } else { + this.nodeIds = options.nodeIds; + this.serializedCC = options.serializedCC; + } + + this.sourceNodeId = options.sourceNodeId; + this.transmitOptions = options.transmitOptions + ?? TransmitOptions.DEFAULT; + if (options.maxSendAttempts != undefined) { + this.maxSendAttempts = options.maxSendAttempts; + } + } + + /** Which Node ID this command originates from */ + public sourceNodeId: number; + + /** The command this message contains */ + public command: MulticastCC | undefined; + /** Options regarding the transmission of the message */ + public transmitOptions: TransmitOptions; + + private _maxSendAttempts: number = 1; + /** The number of times the driver may try to send this message */ + public get maxSendAttempts(): number { + return this._maxSendAttempts; + } + public set maxSendAttempts(value: number) { + this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); + } + + public nodeIds: MulticastDestination; + public override getNodeId(): number | undefined { + // This is multicast, getNodeId must return undefined here + return undefined; + } + + public serializedCC: Buffer | undefined; + public serializeCC(ctx: CCEncodingContext): Buffer { + if (!this.serializedCC) { + if (!this.command) { + throw new ZWaveError( + `Cannot serialize a ${this.constructor.name} without a command`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.serializedCC = this.command.serialize(ctx); + } + return this.serializedCC; + } + + public prepareRetransmission(): void { + this.command?.prepareRetransmission(); + this.serializedCC = undefined; + this.callbackId = undefined; + } + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const serializedCC = this.serializeCC(ctx); + const sourceNodeId = encodeNodeID( + this.sourceNodeId, + ctx.nodeIdType, + ); + const destinationNodeIDs = (this.command?.nodeId ?? this.nodeIds) + .map((id) => encodeNodeID(id, ctx.nodeIdType)); + + this.payload = Buffer.concat([ + sourceNodeId, + // # of target nodes, not # of bytes + Buffer.from([destinationNodeIDs.length]), + ...destinationNodeIDs, + Buffer.from([serializedCC.length]), + // payload + serializedCC, + Buffer.from([this.transmitOptions, this.callbackId]), + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "source node id": this.sourceNodeId, + "target nodes": (this.command?.nodeId ?? this.nodeIds).join( + ", ", + ), + "transmit options": num2hex(this.transmitOptions), + "callback id": this.callbackId ?? "(not set)", + }, + }; + } +} + +export interface SendDataMulticastBridgeRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class SendDataMulticastBridgeRequestTransmitReport + extends SendDataMulticastBridgeRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & SendDataMulticastBridgeRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataMulticastBridgeRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public transmitStatus: TransmitStatus; + + public isOK(): boolean { + return this.transmitStatus === TransmitStatus.OK; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} + +export interface SendDataMulticastBridgeResponseOptions { + wasSent: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SendDataMulticastBridge) +export class SendDataMulticastBridgeResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: SendDataMulticastBridgeResponseOptions & MessageBaseOptions, + ) { + super(options); + + // TODO: Check implementation: + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataMulticastBridgeResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); + } + + public isOK(): boolean { + return this.wasSent; + } + + public wasSent: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was sent": this.wasSent }, + }; + } +} diff --git a/packages/serial/src/serialapi/transport/SendDataMessages.ts b/packages/serial/src/serialapi/transport/SendDataMessages.ts new file mode 100644 index 000000000000..2c5c34439d20 --- /dev/null +++ b/packages/serial/src/serialapi/transport/SendDataMessages.ts @@ -0,0 +1,639 @@ +import { type CommandClass } from "@zwave-js/cc"; +import { + MAX_NODES, + type MessageOrCCLogEntry, + MessagePriority, + type MulticastCC, + type MulticastDestination, + type SerializableTXReport, + type SinglecastCC, + type TXReport, + TransmitOptions, + TransmitStatus, + ZWaveError, + ZWaveErrorCodes, + encodeNodeID, + parseNodeID, +} from "@zwave-js/core"; +import type { CCEncodingContext } from "@zwave-js/host"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, + MessageType, + type SuccessIndicator, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName, num2hex } from "@zwave-js/shared"; +import { clamp } from "alcalzone-shared/math"; +import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; +import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; +import { type MessageWithCC, containsCC } from "../utils"; +import { + encodeTXReport, + parseTXReport, + serializableTXReportToTXReport, + txReportToMessageRecord, +} from "./SendDataShared"; + +export const MAX_SEND_ATTEMPTS = 5; + +@messageTypes(MessageType.Request, FunctionType.SendData) +@priority(MessagePriority.Normal) +export class SendDataRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SendDataRequest.from(raw, ctx); + } else { + return SendDataRequestTransmitReport.from(raw, ctx); + } + } +} + +export type SendDataRequestOptions< + CCType extends CommandClass = CommandClass, +> = + & ( + | { command: CCType } + | { + nodeId: number; + serializedCC: Buffer; + } + ) + & { + transmitOptions?: TransmitOptions; + maxSendAttempts?: number; + }; + +@expectedResponse(FunctionType.SendData) +@expectedCallback(FunctionType.SendData) +export class SendDataRequest + extends SendDataRequestBase + implements MessageWithCC +{ + public constructor( + options: SendDataRequestOptions & MessageBaseOptions, + ) { + super(options); + + if ("command" in options) { + if ( + !options.command.isSinglecast() + && !options.command.isBroadcast() + ) { + throw new ZWaveError( + `SendDataRequest can only be used for singlecast and broadcast CCs`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + this.command = options.command; + this._nodeId = this.command.nodeId; + } else { + this._nodeId = options.nodeId; + this.serializedCC = options.serializedCC; + } + + this.transmitOptions = options.transmitOptions + ?? TransmitOptions.DEFAULT; + if (options.maxSendAttempts != undefined) { + this.maxSendAttempts = options.maxSendAttempts; + } + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataRequest { + let offset = 0; + const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( + raw.payload, + ctx.nodeIdType, + offset, + ); + offset += nodeIdBytes; + const serializedCCLength = raw.payload[offset++]; + const transmitOptions: TransmitOptions = + raw.payload[offset + serializedCCLength]; + const callbackId = raw.payload[offset + 1 + serializedCCLength]; + + const serializedCC = raw.payload.subarray( + offset, + offset + serializedCCLength, + ); + + return new this({ + transmitOptions, + callbackId, + nodeId, + serializedCC, + }); + } + + /** The command this message contains */ + public command: SinglecastCC | undefined; + /** Options regarding the transmission of the message */ + public transmitOptions: TransmitOptions; + + private _maxSendAttempts: number = 1; + /** The number of times the driver may try to send this message */ + public get maxSendAttempts(): number { + return this._maxSendAttempts; + } + public set maxSendAttempts(value: number) { + this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); + } + + private _nodeId: number; + public override getNodeId(): number | undefined { + return this.command?.nodeId ?? this._nodeId; + } + + public serializedCC: Buffer | undefined; + public serializeCC(ctx: CCEncodingContext): Buffer { + if (!this.serializedCC) { + if (!this.command) { + throw new ZWaveError( + `Cannot serialize a ${this.constructor.name} without a command`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.serializedCC = this.command.serialize(ctx); + } + return this.serializedCC; + } + + public prepareRetransmission(): void { + this.command?.prepareRetransmission(); + this.serializedCC = undefined; + this.callbackId = undefined; + } + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID( + this.command?.nodeId ?? this._nodeId, + ctx.nodeIdType, + ); + const serializedCC = this.serializeCC(ctx); + this.payload = Buffer.concat([ + nodeId, + Buffer.from([serializedCC.length]), + serializedCC, + Buffer.from([this.transmitOptions, this.callbackId]), + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "transmit options": num2hex(this.transmitOptions), + "callback id": this.callbackId ?? "(not set)", + }, + }; + } + + public expectsNodeUpdate(): boolean { + return ( + // We can only answer this if the command is known + this.command != undefined + // Only true singlecast commands may expect a response + && this.command.isSinglecast() + // ... and only if the command expects a response + && this.command.expectsCCResponse() + ); + } + + public isExpectedNodeUpdate(msg: Message): boolean { + return ( + // We can only answer this if the command is known + this.command != undefined + && (msg instanceof ApplicationCommandRequest + || msg instanceof BridgeApplicationCommandRequest) + && containsCC(msg) + && this.command.isExpectedCCResponse(msg.command) + ); + } +} + +export interface SendDataRequestTransmitReportOptions { + transmitStatus: TransmitStatus; + txReport?: SerializableTXReport; +} + +export class SendDataRequestTransmitReport extends SendDataRequestBase + implements SuccessIndicator +{ + public constructor( + options: SendDataRequestTransmitReportOptions & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + this.txReport = options.txReport + && serializableTXReportToTXReport(options.txReport); + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + // TODO: Consider NOT parsing this for transmit status other than OK or NoACK + const txReport = parseTXReport( + transmitStatus !== TransmitStatus.NoAck, + raw.payload.subarray(2), + ); + + return new this({ + callbackId, + transmitStatus, + txReport, + }); + } + + public transmitStatus: TransmitStatus; + public txReport: TXReport | undefined; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + this.payload = Buffer.from([ + this.callbackId, + this.transmitStatus, + ]); + if (this.txReport) { + this.payload = Buffer.concat([ + this.payload, + encodeTXReport(this.txReport), + ]); + } + + return super.serialize(ctx); + } + + public isOK(): boolean { + return this.transmitStatus === TransmitStatus.OK; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": + getEnumMemberName(TransmitStatus, this.transmitStatus) + + (this.txReport + ? `, took ${this.txReport.txTicks * 10} ms` + : ""), + ...(this.txReport + ? txReportToMessageRecord(this.txReport) + : {}), + }, + }; + } +} + +export interface SendDataResponseOptions { + wasSent: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SendData) +export class SendDataResponse extends Message implements SuccessIndicator { + public constructor( + options: SendDataResponseOptions & MessageBaseOptions, + ) { + super(options); + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); + } + + public wasSent: boolean; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.wasSent ? 1 : 0]); + return super.serialize(ctx); + } + + isOK(): boolean { + return this.wasSent; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was sent": this.wasSent }, + }; + } +} + +@messageTypes(MessageType.Request, FunctionType.SendDataMulticast) +@priority(MessagePriority.Normal) +export class SendDataMulticastRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataMulticastRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SendDataMulticastRequest.from(raw, ctx); + } else { + return SendDataMulticastRequestTransmitReport.from(raw, ctx); + } + } +} + +export type SendDataMulticastRequestOptions = + & ( + | { command: CCType } + | { + nodeIds: MulticastDestination; + serializedCC: Buffer; + } + ) + & { + transmitOptions?: TransmitOptions; + maxSendAttempts?: number; + }; + +@expectedResponse(FunctionType.SendDataMulticast) +@expectedCallback(FunctionType.SendDataMulticast) +export class SendDataMulticastRequest< + CCType extends CommandClass = CommandClass, +> extends SendDataMulticastRequestBase implements MessageWithCC { + public constructor( + options: SendDataMulticastRequestOptions & MessageBaseOptions, + ) { + super(options); + + if ("command" in options) { + if (!options.command.isMulticast()) { + throw new ZWaveError( + `SendDataMulticastRequest can only be used for multicast CCs`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (options.command.nodeId.length === 0) { + throw new ZWaveError( + `At least one node must be targeted`, + ZWaveErrorCodes.Argument_Invalid, + ); + } else if ( + options.command.nodeId.some((n) => n < 1 || n > MAX_NODES) + ) { + throw new ZWaveError( + `All node IDs must be between 1 and ${MAX_NODES}!`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + + this.command = options.command; + this.nodeIds = this.command.nodeId; + } else { + this.nodeIds = options.nodeIds; + this.serializedCC = options.serializedCC; + } + this.transmitOptions = options.transmitOptions + ?? TransmitOptions.DEFAULT; + if (options.maxSendAttempts != undefined) { + this.maxSendAttempts = options.maxSendAttempts; + } + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendDataMulticastRequest { + const numNodeIDs = raw.payload[0]; + let offset = 1; + const nodeIds: number[] = []; + for (let i = 0; i < numNodeIDs; i++) { + const { nodeId, bytesRead } = parseNodeID( + raw.payload, + ctx.nodeIdType, + offset, + ); + nodeIds.push(nodeId); + offset += bytesRead; + } + const serializedCCLength = raw.payload[offset]; + offset++; + const serializedCC = raw.payload.subarray( + offset, + offset + serializedCCLength, + ); + offset += serializedCCLength; + const transmitOptions: TransmitOptions = raw.payload[offset]; + + offset++; + const callbackId: any = raw.payload[offset]; + + return new this({ + transmitOptions, + callbackId, + nodeIds: nodeIds as MulticastDestination, + serializedCC, + }); + } + + /** The command this message contains */ + public command: MulticastCC | undefined; + /** Options regarding the transmission of the message */ + public transmitOptions: TransmitOptions; + + private _maxSendAttempts: number = 1; + /** The number of times the driver may try to send this message */ + public get maxSendAttempts(): number { + return this._maxSendAttempts; + } + public set maxSendAttempts(value: number) { + this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); + } + + public nodeIds: MulticastDestination; + public override getNodeId(): number | undefined { + // This is multicast, getNodeId must return undefined here + return undefined; + } + + public serializedCC: Buffer | undefined; + public serializeCC(ctx: CCEncodingContext): Buffer { + if (!this.serializedCC) { + if (!this.command) { + throw new ZWaveError( + `Cannot serialize a ${this.constructor.name} without a command`, + ZWaveErrorCodes.Argument_Invalid, + ); + } + this.serializedCC = this.command.serialize(ctx); + } + return this.serializedCC; + } + + public prepareRetransmission(): void { + this.command?.prepareRetransmission(); + this.serializedCC = undefined; + this.callbackId = undefined; + } + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const serializedCC = this.serializeCC(ctx); + const destinationNodeIDs = (this.command?.nodeId ?? this.nodeIds) + .map((id) => encodeNodeID(id, ctx.nodeIdType)); + this.payload = Buffer.concat([ + // # of target nodes, not # of bytes + Buffer.from([destinationNodeIDs.length]), + ...destinationNodeIDs, + Buffer.from([serializedCC.length]), + // payload + serializedCC, + Buffer.from([this.transmitOptions, this.callbackId]), + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "target nodes": (this.command?.nodeId ?? this.nodeIds).join( + ", ", + ), + "transmit options": num2hex(this.transmitOptions), + "callback id": this.callbackId ?? "(not set)", + }, + }; + } +} + +export interface SendDataMulticastRequestTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class SendDataMulticastRequestTransmitReport + extends SendDataMulticastRequestBase + implements SuccessIndicator +{ + public constructor( + options: + & SendDataMulticastRequestTransmitReportOptions + & MessageBaseOptions, + ) { + super(options); + + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataMulticastRequestTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public transmitStatus: TransmitStatus; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + return super.serialize(ctx); + } + + public isOK(): boolean { + return this.transmitStatus === TransmitStatus.OK; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} + +export interface SendDataMulticastResponseOptions { + wasSent: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SendDataMulticast) +export class SendDataMulticastResponse extends Message + implements SuccessIndicator +{ + public constructor( + options: SendDataMulticastResponseOptions & MessageBaseOptions, + ) { + super(options); + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendDataMulticastResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); + } + + public wasSent: boolean; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.payload = Buffer.from([this.wasSent ? 1 : 0]); + return super.serialize(ctx); + } + + public isOK(): boolean { + return this.wasSent; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was sent": this.wasSent }, + }; + } +} + +@messageTypes(MessageType.Request, FunctionType.SendDataAbort) +@priority(MessagePriority.Controller) +export class SendDataAbort extends Message {} diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendDataShared.ts b/packages/serial/src/serialapi/transport/SendDataShared.ts similarity index 90% rename from packages/zwave-js/src/lib/serialapi/transport/SendDataShared.ts rename to packages/serial/src/serialapi/transport/SendDataShared.ts index 801392fb3c89..d227e42cf985 100644 --- a/packages/zwave-js/src/lib/serialapi/transport/SendDataShared.ts +++ b/packages/serial/src/serialapi/transport/SendDataShared.ts @@ -1,5 +1,6 @@ import { type MessageRecord, + ProtocolDataRate, type RSSI, RssiError, type SerializableTXReport, @@ -89,9 +90,9 @@ export function parseTXReport( payload: Buffer, ): TXReport | undefined { if (payload.length < 17) return; + const numRepeaters = payload[2]; const ret: TXReport = { txTicks: payload.readUInt16BE(0), - numRepeaters: payload[2], ackRSSI: includeACK ? parseRSSI(payload, 3) : undefined, ackRepeaterRSSI: includeACK ? [ @@ -125,21 +126,47 @@ export function parseTXReport( : undefined, }; // Remove unused repeaters from arrays - const firstMissingRepeater = ret.repeaterNodeIds.indexOf(0); ret.repeaterNodeIds = ret.repeaterNodeIds.slice( 0, - firstMissingRepeater, + numRepeaters, ) as any; if (ret.ackRepeaterRSSI) { ret.ackRepeaterRSSI = ret.ackRepeaterRSSI.slice( 0, - firstMissingRepeater, + numRepeaters, ) as any; } return stripUndefined(ret as any) as any; } +export function serializableTXReportToTXReport( + report: SerializableTXReport, +): TXReport { + return { + txTicks: report.txTicks, + ackRSSI: report.ackRSSI, + ackRepeaterRSSI: report.ackRepeaterRSSI, + ackChannelNo: report.ackChannelNo, + txChannelNo: report.txChannelNo ?? 0, + routeSchemeState: report.routeSchemeState ?? 0, + repeaterNodeIds: report.repeaterNodeIds ?? [], + beam1000ms: report.beam1000ms ?? false, + beam250ms: report.beam250ms ?? false, + routeSpeed: report.routeSpeed ?? ProtocolDataRate.ZWave_100k, + routingAttempts: report.routingAttempts ?? 1, + failedRouteLastFunctionalNodeId: report.failedRouteLastFunctionalNodeId, + failedRouteFirstNonFunctionalNodeId: + report.failedRouteFirstNonFunctionalNodeId, + txPower: report.txPower, + measuredNoiseFloor: report.measuredNoiseFloor, + destinationAckTxPower: report.destinationAckTxPower, + destinationAckMeasuredRSSI: report.destinationAckMeasuredRSSI, + destinationAckMeasuredNoiseFloor: + report.destinationAckMeasuredNoiseFloor, + }; +} + export function encodeTXReport(report: SerializableTXReport): Buffer { const ret = Buffer.alloc(24, 0); ret.writeUInt16BE(report.txTicks, 0); diff --git a/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts b/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts new file mode 100644 index 000000000000..9a64f56ca3e4 --- /dev/null +++ b/packages/serial/src/serialapi/transport/SendTestFrameMessages.ts @@ -0,0 +1,187 @@ +import { Powerlevel } from "@zwave-js/cc"; +import { + type MessageOrCCLogEntry, + MessagePriority, + TransmitStatus, + encodeNodeID, + parseNodeID, +} from "@zwave-js/core"; +import { + FunctionType, + Message, + type MessageBaseOptions, + type MessageEncodingContext, + MessageOrigin, + type MessageParsingContext, + type MessageRaw, + MessageType, + type SuccessIndicator, + expectedCallback, + expectedResponse, + messageTypes, + priority, +} from "@zwave-js/serial"; +import { getEnumMemberName } from "@zwave-js/shared"; + +@messageTypes(MessageType.Request, FunctionType.SendTestFrame) +@priority(MessagePriority.Normal) +export class SendTestFrameRequestBase extends Message { + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendTestFrameRequestBase { + if (ctx.origin === MessageOrigin.Host) { + return SendTestFrameRequest.from(raw, ctx); + } else { + return SendTestFrameTransmitReport.from(raw, ctx); + } + } +} + +export interface SendTestFrameRequestOptions { + testNodeId: number; + powerlevel: Powerlevel; +} + +@expectedResponse(FunctionType.SendTestFrame) +@expectedCallback(FunctionType.SendTestFrame) +export class SendTestFrameRequest extends SendTestFrameRequestBase { + public constructor( + options: SendTestFrameRequestOptions & MessageBaseOptions, + ) { + super(options); + this.testNodeId = options.testNodeId; + this.powerlevel = options.powerlevel; + } + + public static from( + raw: MessageRaw, + ctx: MessageParsingContext, + ): SendTestFrameRequest { + let offset = 0; + const { nodeId: testNodeId, bytesRead: nodeIdBytes } = parseNodeID( + raw.payload, + ctx.nodeIdType, + offset, + ); + offset += nodeIdBytes; + const powerlevel: Powerlevel = raw.payload[offset++]; + const callbackId = raw.payload[offset++]; + + return new this({ + testNodeId, + powerlevel, + callbackId, + }); + } + + public testNodeId: number; + public powerlevel: Powerlevel; + + public serialize(ctx: MessageEncodingContext): Buffer { + this.assertCallbackId(); + const nodeId = encodeNodeID(this.testNodeId, ctx.nodeIdType); + this.payload = Buffer.concat([ + nodeId, + Buffer.from([ + this.powerlevel, + this.callbackId, + ]), + ]); + + return super.serialize(ctx); + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "test node id": this.testNodeId, + powerlevel: getEnumMemberName(Powerlevel, this.powerlevel), + "callback id": this.callbackId ?? "(not set)", + }, + }; + } +} + +export interface SendTestFrameResponseOptions { + wasSent: boolean; +} + +@messageTypes(MessageType.Response, FunctionType.SendTestFrame) +export class SendTestFrameResponse extends Message { + public constructor( + options: SendTestFrameResponseOptions & MessageBaseOptions, + ) { + super(options); + this.wasSent = options.wasSent; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendTestFrameResponse { + const wasSent = raw.payload[0] !== 0; + + return new this({ + wasSent, + }); + } + + public readonly wasSent: boolean; + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { "was sent": this.wasSent }, + }; + } +} + +export interface SendTestFrameTransmitReportOptions { + transmitStatus: TransmitStatus; +} + +export class SendTestFrameTransmitReport extends SendTestFrameRequestBase + implements SuccessIndicator +{ + public constructor( + options: SendTestFrameTransmitReportOptions & MessageBaseOptions, + ) { + super(options); + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } + + public static from( + raw: MessageRaw, + _ctx: MessageParsingContext, + ): SendTestFrameTransmitReport { + const callbackId = raw.payload[0]; + const transmitStatus: TransmitStatus = raw.payload[1]; + + return new this({ + callbackId, + transmitStatus, + }); + } + + public transmitStatus: TransmitStatus; + + isOK(): boolean { + return this.transmitStatus === TransmitStatus.OK; + } + + public toLogEntry(): MessageOrCCLogEntry { + return { + ...super.toLogEntry(), + message: { + "callback id": this.callbackId ?? "(not set)", + "transmit status": getEnumMemberName( + TransmitStatus, + this.transmitStatus, + ), + }, + }; + } +} diff --git a/packages/serial/src/serialapi/utils.ts b/packages/serial/src/serialapi/utils.ts new file mode 100644 index 000000000000..42e157f97377 --- /dev/null +++ b/packages/serial/src/serialapi/utils.ts @@ -0,0 +1,54 @@ +import { CommandClass } from "@zwave-js/cc"; +import { type Message } from "@zwave-js/serial"; +import { ApplicationCommandRequest } from "./application/ApplicationCommandRequest"; +import { BridgeApplicationCommandRequest } from "./application/BridgeApplicationCommandRequest"; +import { type SendDataMessage, isSendData } from "./transport/SendDataShared"; + +export type CommandRequest = + | ApplicationCommandRequest + | BridgeApplicationCommandRequest; + +export function isCommandRequest( + msg: Message, +): msg is CommandRequest { + return msg instanceof ApplicationCommandRequest + || msg instanceof BridgeApplicationCommandRequest; +} + +export interface MessageWithCC { + serializedCC: Buffer | undefined; + command: CommandClass | undefined; +} + +export function isMessageWithCC( + msg: Message, +): msg is + | SendDataMessage + | CommandRequest +{ + return isSendData(msg) || isCommandRequest(msg); +} + +export interface ContainsSerializedCC { + serializedCC: Buffer; +} + +export function containsSerializedCC( + container: T | undefined, +): container is T & ContainsSerializedCC { + return !!container + && "serializedCC" in container + && Buffer.isBuffer(container.serializedCC); +} + +export interface ContainsCC { + command: T; +} + +export function containsCC( + container: T | undefined, +): container is T & ContainsCC { + return !!container + && "command" in container + && container.command instanceof CommandClass; +} diff --git a/packages/serial/src/DisconnectError.ts b/packages/serial/src/serialport/DisconnectError.ts similarity index 100% rename from packages/serial/src/DisconnectError.ts rename to packages/serial/src/serialport/DisconnectError.ts diff --git a/packages/serial/src/ZWaveSerialPort.test.ts b/packages/serial/src/serialport/ZWaveSerialPort.test.ts similarity index 96% rename from packages/serial/src/ZWaveSerialPort.test.ts rename to packages/serial/src/serialport/ZWaveSerialPort.test.ts index 36e0c9b13d5c..3376f8b466dd 100644 --- a/packages/serial/src/ZWaveSerialPort.test.ts +++ b/packages/serial/src/serialport/ZWaveSerialPort.test.ts @@ -2,9 +2,9 @@ import { wait } from "alcalzone-shared/async"; import ava, { type TestFn } from "ava"; import { PassThrough } from "node:stream"; import sinon from "sinon"; -import { MessageHeaders } from "./MessageHeaders"; -import { createAndOpenMockedZWaveSerialPort } from "./MockSerialPort"; -import type { MockPortBinding } from "./SerialPortBindingMock"; +import { MessageHeaders } from "../message/MessageHeaders"; +import { createAndOpenMockedZWaveSerialPort } from "../mock/MockSerialPort"; +import type { MockPortBinding } from "../mock/SerialPortBindingMock"; import type { ZWaveSerialPort } from "./ZWaveSerialPort"; interface TestContext { diff --git a/packages/serial/src/ZWaveSerialPort.ts b/packages/serial/src/serialport/ZWaveSerialPort.ts similarity index 100% rename from packages/serial/src/ZWaveSerialPort.ts rename to packages/serial/src/serialport/ZWaveSerialPort.ts diff --git a/packages/serial/src/ZWaveSerialPortBase.ts b/packages/serial/src/serialport/ZWaveSerialPortBase.ts similarity index 97% rename from packages/serial/src/ZWaveSerialPortBase.ts rename to packages/serial/src/serialport/ZWaveSerialPortBase.ts index 0f445df97f77..50bf85b7ae8b 100644 --- a/packages/serial/src/ZWaveSerialPortBase.ts +++ b/packages/serial/src/serialport/ZWaveSerialPortBase.ts @@ -2,16 +2,16 @@ import type { ZWaveLogContainer } from "@zwave-js/core"; import { Mixin } from "@zwave-js/shared"; import { EventEmitter } from "node:events"; import { PassThrough, type Readable, type Writable } from "node:stream"; -import { SerialLogger } from "./Logger"; -import { MessageHeaders } from "./MessageHeaders"; -import { type ZWaveSerialPortImplementation } from "./ZWaveSerialPortImplementation"; +import { SerialLogger } from "../log/Logger"; +import { MessageHeaders } from "../message/MessageHeaders"; import { type BootloaderChunk, BootloaderParser, BootloaderScreenParser, bootloaderMenuPreamble, -} from "./parsers/BootloaderParsers"; -import { SerialAPIParser } from "./parsers/SerialAPIParser"; +} from "../parsers/BootloaderParsers"; +import { SerialAPIParser } from "../parsers/SerialAPIParser"; +import { type ZWaveSerialPortImplementation } from "./ZWaveSerialPortImplementation"; export type ZWaveSerialChunk = | MessageHeaders.ACK diff --git a/packages/serial/src/ZWaveSerialPortImplementation.ts b/packages/serial/src/serialport/ZWaveSerialPortImplementation.ts similarity index 100% rename from packages/serial/src/ZWaveSerialPortImplementation.ts rename to packages/serial/src/serialport/ZWaveSerialPortImplementation.ts diff --git a/packages/serial/src/ZWaveSocket.ts b/packages/serial/src/serialport/ZWaveSocket.ts similarity index 100% rename from packages/serial/src/ZWaveSocket.ts rename to packages/serial/src/serialport/ZWaveSocket.ts diff --git a/packages/serial/src/ZWaveSocketOptions.ts b/packages/serial/src/serialport/ZWaveSocketOptions.ts similarity index 100% rename from packages/serial/src/ZWaveSocketOptions.ts rename to packages/serial/src/serialport/ZWaveSocketOptions.ts diff --git a/packages/serial/src/ZnifferSerialPort.ts b/packages/serial/src/zniffer/ZnifferSerialPort.ts similarity index 96% rename from packages/serial/src/ZnifferSerialPort.ts rename to packages/serial/src/zniffer/ZnifferSerialPort.ts index 87823bd14f26..08b4fd0c595a 100644 --- a/packages/serial/src/ZnifferSerialPort.ts +++ b/packages/serial/src/zniffer/ZnifferSerialPort.ts @@ -4,7 +4,7 @@ import { type ZWaveLogContainer, } from "@zwave-js/core"; import { SerialPort } from "serialport"; -import type { DisconnectError } from "./DisconnectError"; +import type { DisconnectError } from "../serialport/DisconnectError"; import { ZnifferSerialPortBase } from "./ZnifferSerialPortBase"; // FIXME: This class is identical to ZWaveSerialPort, except for the class name and the base class diff --git a/packages/serial/src/ZnifferSerialPortBase.ts b/packages/serial/src/zniffer/ZnifferSerialPortBase.ts similarity index 96% rename from packages/serial/src/ZnifferSerialPortBase.ts rename to packages/serial/src/zniffer/ZnifferSerialPortBase.ts index b23efdcf6f66..c389ccb97da7 100644 --- a/packages/serial/src/ZnifferSerialPortBase.ts +++ b/packages/serial/src/zniffer/ZnifferSerialPortBase.ts @@ -2,9 +2,9 @@ import type { ZWaveLogContainer } from "@zwave-js/core"; import { Mixin } from "@zwave-js/shared"; import { EventEmitter } from "node:events"; import { PassThrough, type Readable, type Writable } from "node:stream"; -import { SerialLogger } from "./Logger"; -import { type ZWaveSerialPortImplementation } from "./ZWaveSerialPortImplementation"; -import { ZnifferParser } from "./parsers/ZnifferParser"; +import { SerialLogger } from "../log/Logger"; +import { ZnifferParser } from "../parsers/ZnifferParser"; +import { type ZWaveSerialPortImplementation } from "../serialport/ZWaveSerialPortImplementation"; export interface ZnifferSerialPortEventCallbacks { error: (e: Error) => void; diff --git a/packages/serial/src/ZnifferSocket.ts b/packages/serial/src/zniffer/ZnifferSocket.ts similarity index 96% rename from packages/serial/src/ZnifferSocket.ts rename to packages/serial/src/zniffer/ZnifferSocket.ts index 3fe8ea1341d1..15183bf4e1f6 100644 --- a/packages/serial/src/ZnifferSocket.ts +++ b/packages/serial/src/zniffer/ZnifferSocket.ts @@ -4,7 +4,7 @@ import { type ZWaveLogContainer, } from "@zwave-js/core"; import * as net from "node:net"; -import { type ZWaveSocketOptions } from "./ZWaveSocketOptions"; +import { type ZWaveSocketOptions } from "../serialport/ZWaveSocketOptions"; import { ZnifferSerialPortBase } from "./ZnifferSerialPortBase"; // FIXME: This class is identical to ZWaveSocket, except for the class name and the base class diff --git a/packages/serial/tsconfig.build.json b/packages/serial/tsconfig.build.json index 57ae7c3b43ef..a2fb52512b12 100644 --- a/packages/serial/tsconfig.build.json +++ b/packages/serial/tsconfig.build.json @@ -8,6 +8,9 @@ "customConditions": [] }, "references": [ + { + "path": "../cc/tsconfig.build.json" + }, { "path": "../core/tsconfig.build.json" }, diff --git a/packages/testing/src/MockController.ts b/packages/testing/src/MockController.ts index b2fc979d71d5..4cb57a23b8e2 100644 --- a/packages/testing/src/MockController.ts +++ b/packages/testing/src/MockController.ts @@ -1,14 +1,24 @@ -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 { + type FunctionType, 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 { @@ -43,7 +53,7 @@ export class MockController { this.serial.on("write", async (data) => { // Execute hooks for inspecting the raw data first for (const behavior of this.behaviors) { - if (await behavior.onHostData?.(this.host, this, data)) { + if (await behavior.onHostData?.(this, data)) { return; } } @@ -56,26 +66,58 @@ 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 requestStorage = 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; @@ -84,35 +126,39 @@ 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, + // FIXME: Take from the controller capabilities + sdkVersion: undefined, + requestStorage, }; 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; @@ -152,8 +198,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 */ @@ -196,10 +240,9 @@ export class MockController { let msg: Message; try { - msg = Message.from(this.host, { - data, + msg = Message.parse(data, { + ...this.parsingContext, origin: MessageOrigin.Host, - parseCCs: false, }); this._receivedHostMessages.push(msg); if (this.autoAckHostMessages) { @@ -220,7 +263,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; } } @@ -308,10 +351,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, @@ -345,6 +388,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); @@ -389,7 +453,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; } @@ -498,19 +562,16 @@ export interface MockControllerBehavior { * Return `true` to indicate that the data has been handled and should not be processed further. */ onHostData?: ( - host: ZWaveHost, controller: MockController, data: Buffer, ) => Promise | boolean | undefined; /** 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/testing/testing.api.md b/packages/testing/testing.api.md index 21605ce7a58b..b7a5a6a2f5aa 100644 --- a/packages/testing/testing.api.md +++ b/packages/testing/testing.api.md @@ -4,6 +4,8 @@ ```ts +import type { CCEncodingContext } from '@zwave-js/host'; +import { CCId } from '@zwave-js/core'; import type { ColorComponent } from '@zwave-js/cc'; import { CommandClass } from '@zwave-js/cc'; import { CommandClasses } from '@zwave-js/core'; @@ -11,17 +13,20 @@ import { CommandClassInfo } from '@zwave-js/core'; import type { ConfigValue } from '@zwave-js/core'; import type { ConfigValueFormat } from '@zwave-js/core'; import { FunctionType } from '@zwave-js/serial/safe'; -import { ICommandClass } from '@zwave-js/core'; import type { KeypadMode } from '@zwave-js/cc'; +import type { MaybeUnknown } from '@zwave-js/core'; import { Message } from '@zwave-js/serial'; +import { MessageEncodingContext } from '@zwave-js/serial'; +import { MessageParsingContext } from '@zwave-js/serial'; import type { MockPortBinding } from '@zwave-js/serial/mock'; import { NodeProtocolInfoAndDeviceClass } from '@zwave-js/core'; +import { SecurityManagers } from '@zwave-js/core'; +import type { SwitchType } from '@zwave-js/cc'; import type { ThermostatMode } from '@zwave-js/cc'; import type { ThermostatSetpointType } from '@zwave-js/cc'; import type { UserIDStatus } from '@zwave-js/cc'; import type { WindowCoveringParameter } from '@zwave-js/cc'; import { ZWaveApiVersion } from '@zwave-js/core/safe'; -import type { ZWaveHost } from '@zwave-js/host'; import { ZWaveLibraryTypes } from '@zwave-js/core/safe'; // Warning: (ae-missing-release-tag) "BinarySensorCCCapabilities" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -34,6 +39,14 @@ export interface BinarySensorCCCapabilities { supportedSensorTypes: number[]; } +// Warning: (ae-missing-release-tag) "BinarySwitchCCCapabilities" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface BinarySwitchCCCapabilities { + // (undocumented) + defaultValue?: MaybeUnknown; +} + // Warning: (ae-missing-release-tag) "ccCaps" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -51,7 +64,9 @@ export type CCSpecificCapabilities = { [CommandClasses.Configuration]: ConfigurationCCCapabilities; [CommandClasses.Notification]: NotificationCCCapabilities; [48]: BinarySensorCCCapabilities; + [0x25]: BinarySwitchCCCapabilities; [49]: MultilevelSensorCCCapabilities; + [0x26]: MultilevelSwitchCCCapabilities; [51]: ColorSwitchCCCapabilities; [121]: SoundSwitchCCCapabilities; [106]: WindowCoveringCCCapabilities; @@ -220,6 +235,8 @@ export class MockController { // (undocumented) destroy(): void; // (undocumented) + encodingContext: MessageEncodingContext; + // (undocumented) execute(): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen expectHostACK(timeout: number): Promise; @@ -228,18 +245,25 @@ export class MockController { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen expectNodeACK(node: MockNode, timeout: number): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - expectNodeCC(node: MockNode, timeout: number, predicate: (cc: ICommandClass) => cc is T): Promise; + expectNodeCC(node: MockNode, timeout: number, predicate: (cc: CCId) => cc is T): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen expectNodeFrame(node: MockNode, timeout: number, predicate: (msg: MockZWaveFrame) => msg is T): Promise; // (undocumented) - readonly host: ZWaveHost; + homeId: number; // (undocumented) get nodes(): ReadonlyMap; onNodeFrame(node: MockNode, frame: MockZWaveFrame): Promise; // (undocumented) + ownNodeId: number; + // (undocumented) + parsingContext: MessageParsingContext; + // (undocumented) get receivedHostMessages(): readonly Readonly[]; // (undocumented) removeNode(node: MockNode): void; + // (undocumented) + securityManagers: SecurityManagers; + sendMessageToHost(msg: Message, fromNode?: MockNode): Promise; sendToHost(data: Buffer): Promise; sendToNode(node: MockNode, frame: LazyMockZWaveFrame): Promise; // (undocumented) @@ -251,8 +275,8 @@ export class MockController { // // @public (undocumented) export interface MockControllerBehavior { - onHostMessage?: (host: ZWaveHost, controller: MockController, msg: Message) => Promise | boolean | undefined; - onNodeFrame?: (host: ZWaveHost, controller: MockController, node: MockNode, frame: MockZWaveFrame) => Promise | boolean | undefined; + onHostMessage?: (controller: MockController, msg: Message) => Promise | boolean | undefined; + onNodeFrame?: (controller: MockController, node: MockNode, frame: MockZWaveFrame) => Promise | boolean | undefined; } // Warning: (ae-missing-release-tag) "MockControllerOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -325,6 +349,8 @@ export class MockNode { // (undocumented) defineBehavior(...behaviors: MockNodeBehavior[]): void; // (undocumented) + encodingContext: CCEncodingContext; + // (undocumented) readonly endpoints: Map; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen expectControllerACK(timeout: number): Promise; @@ -333,13 +359,13 @@ export class MockNode { // (undocumented) getCCCapabilities(ccId: T, endpointIndex?: number): Partial> | undefined; // (undocumented) - readonly host: ZWaveHost; - // (undocumented) readonly id: number; // (undocumented) readonly implementedCCs: Map; onControllerFrame(frame: MockZWaveFrame): Promise; removeCC(cc: CommandClasses): void; + // (undocumented) + securityManagers: SecurityManagers; sendToController(frame: LazyMockZWaveFrame): Promise; readonly state: Map; } @@ -436,6 +462,16 @@ export interface MultilevelSensorCCCapabilities { }>; } +// Warning: (ae-missing-release-tag) "MultilevelSwitchCCCapabilities" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MultilevelSwitchCCCapabilities { + // (undocumented) + defaultValue?: MaybeUnknown; + // (undocumented) + primarySwitchType: SwitchType; +} + // Warning: (ae-missing-release-tag) "NotificationCCCapabilities" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -536,21 +572,20 @@ export interface WindowCoveringCCCapabilities { // Warnings were encountered during analysis: // -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ColorSwitchCC.ts:478:9 - (TS2345) Argument of type '("index" | "warmWhite" | "coldWhite" | "red" | "green" | "blue" | "amber" | "cyan" | "purple" | undefined)[]' is not assignable to parameter of type 'readonly (string | number | symbol)[]'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ColorSwitchCC.ts:480:9 - (TS2345) Argument of type '("index" | "warmWhite" | "coldWhite" | "red" | "green" | "blue" | "amber" | "cyan" | "purple" | undefined)[]' is not assignable to parameter of type 'readonly (string | number | symbol)[]'. // Type 'string | undefined' is not assignable to type 'string | number | symbol'. // Type 'undefined' is not assignable to type 'string | number | symbol'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1273:41 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1283:36 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1280:20 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1290:20 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1398:40 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1414:35 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1456:3 - (TS2322) Type 'Security2Extension | undefined' is not assignable to type 'MGRPExtension | undefined'. -// Property 'groupId' is missing in type 'Security2Extension' but required in type 'MGRPExtension'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1467:3 - (TS2322) Type 'Security2Extension | undefined' is not assignable to type 'MPANExtension | undefined'. -// Type 'Security2Extension' is missing the following properties from type 'MPANExtension': groupId, innerMPANState -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1481:25 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1542:19 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:450:24 - (TS2339) Property 'groupId' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:458:24 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1655:20 - (TS2339) Property 'groupId' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1658:34 - (TS2339) Property 'innerMPANState' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1808:19 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. // (No @packageDocumentation comment for this package) diff --git a/packages/zwave-js/src/Controller.ts b/packages/zwave-js/src/Controller.ts index c7dfbc004b3e..970e6c700640 100644 --- a/packages/zwave-js/src/Controller.ts +++ b/packages/zwave-js/src/Controller.ts @@ -11,6 +11,8 @@ export { isRssiError, } from "@zwave-js/core/safe"; export type { RSSI, TXReport } from "@zwave-js/core/safe"; +export type { ZWaveApiVersion, ZWaveLibraryTypes } from "@zwave-js/core/safe"; +export { SerialAPISetupCommand } from "@zwave-js/serial/serialapi"; export { ZWaveController } from "./lib/controller/Controller"; export type { ControllerEvents } from "./lib/controller/Controller"; export type { ControllerStatistics } from "./lib/controller/ControllerStatistics"; @@ -28,8 +30,3 @@ export type { RebuildRoutesStatus, SDKVersion, } from "./lib/controller/_Types"; -export type { - ZWaveApiVersion, - ZWaveLibraryTypes, -} from "./lib/serialapi/_Types"; -export { SerialAPISetupCommand } from "./lib/serialapi/capability/SerialAPISetupMessages"; diff --git a/packages/zwave-js/src/Controller_safe.ts b/packages/zwave-js/src/Controller_safe.ts index 656594c192b9..c5645007f296 100644 --- a/packages/zwave-js/src/Controller_safe.ts +++ b/packages/zwave-js/src/Controller_safe.ts @@ -5,6 +5,7 @@ export { isRssiError, } from "@zwave-js/core/safe"; export type { RSSI, TXReport } from "@zwave-js/core/safe"; +export type { ZWaveLibraryTypes } from "@zwave-js/core/safe"; export type { ControllerStatistics } from "./lib/controller/ControllerStatistics"; export { ZWaveFeature } from "./lib/controller/Features"; export * from "./lib/controller/Inclusion"; @@ -18,4 +19,3 @@ export type { RebuildRoutesStatus, SDKVersion, } from "./lib/controller/_Types"; -export type { ZWaveLibraryTypes } from "./lib/serialapi/_Types"; diff --git a/packages/zwave-js/src/Driver.ts b/packages/zwave-js/src/Driver.ts index 061315774056..b11f18d0307b 100644 --- a/packages/zwave-js/src/Driver.ts +++ b/packages/zwave-js/src/Driver.ts @@ -7,6 +7,16 @@ export type { ResponsePredicate, ResponseRole, } from "@zwave-js/serial"; +export { + type CommandRequest, + type ContainsCC, + type ContainsSerializedCC, + type MessageWithCC, + containsCC, + containsSerializedCC, + isCommandRequest, + isMessageWithCC, +} from "@zwave-js/serial/serialapi"; export { Driver, libName, libVersion } from "./lib/driver/Driver"; export type { EditableZWaveOptions, 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..211f40671d32 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, @@ -75,9 +75,11 @@ import { UNKNOWN_STATE, type UnknownZWaveChipType, ValueDB, + type ZWaveApiVersion, type ZWaveDataRate, ZWaveError, ZWaveErrorCodes, + ZWaveLibraryTypes, authHomeIdFromDSK, averageRSSI, computePRK, @@ -96,6 +98,10 @@ import { isZWaveError, nwiHomeIdFromDSK, parseBitMask, + sdkVersionGt, + sdkVersionGte, + sdkVersionLt, + sdkVersionLte, securityClassIsS2, securityClassOrder, } from "@zwave-js/core"; @@ -116,43 +122,6 @@ import { type SuccessIndicator, XModemMessageHeaders, } from "@zwave-js/serial"; -import { - Mixin, - type ReadonlyObjectKeyMap, - type ReadonlyThrowingMap, - type ThrowingMap, - TypedEventEmitter, - cloneDeep, - createThrowingMap, - flatMap, - getEnumMemberName, - getErrorMessage, - noop, - num2hex, - pick, -} 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 { isObject } from "alcalzone-shared/typeguards"; -import crypto from "node:crypto"; -import type { Driver } from "../driver/Driver"; -import { cacheKeyUtils, cacheKeys } from "../driver/NetworkCache"; -import type { StatisticsEventCallbacks } from "../driver/Statistics"; -import { type TaskBuilder, TaskPriority } from "../driver/Task"; -import { DeviceClass } from "../node/DeviceClass"; -import { ZWaveNode } from "../node/Node"; -import { VirtualNode } from "../node/VirtualNode"; -import { - InterviewStage, - type LifelineRoutes, - NodeStatus, -} from "../node/_Types"; -import { type ZWaveApiVersion, ZWaveLibraryTypes } from "../serialapi/_Types"; import { type ApplicationUpdateRequest, ApplicationUpdateRequestNodeAdded, @@ -161,42 +130,42 @@ import { ApplicationUpdateRequestSUCIdChanged, ApplicationUpdateRequestSmartStartHomeIDReceived, ApplicationUpdateRequestSmartStartLongRangeHomeIDReceived, -} from "../serialapi/application/ApplicationUpdateRequest"; +} from "@zwave-js/serial/serialapi"; import { ShutdownRequest, type ShutdownResponse, -} from "../serialapi/application/ShutdownMessages"; +} from "@zwave-js/serial/serialapi"; import { GetControllerCapabilitiesRequest, type GetControllerCapabilitiesResponse, -} from "../serialapi/capability/GetControllerCapabilitiesMessages"; +} from "@zwave-js/serial/serialapi"; import { GetControllerVersionRequest, type GetControllerVersionResponse, -} from "../serialapi/capability/GetControllerVersionMessages"; +} from "@zwave-js/serial/serialapi"; import { GetLongRangeNodesRequest, type GetLongRangeNodesResponse, -} from "../serialapi/capability/GetLongRangeNodesMessages"; +} from "@zwave-js/serial/serialapi"; import { GetProtocolVersionRequest, type GetProtocolVersionResponse, -} from "../serialapi/capability/GetProtocolVersionMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSerialApiCapabilitiesRequest, type GetSerialApiCapabilitiesResponse, -} from "../serialapi/capability/GetSerialApiCapabilitiesMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSerialApiInitDataRequest, type GetSerialApiInitDataResponse, -} from "../serialapi/capability/GetSerialApiInitDataMessages"; -import { HardResetRequest } from "../serialapi/capability/HardResetRequest"; +} from "@zwave-js/serial/serialapi"; +import { HardResetRequest } from "@zwave-js/serial/serialapi"; import { GetLongRangeChannelRequest, type GetLongRangeChannelResponse, SetLongRangeChannelRequest, type SetLongRangeChannelResponse, -} from "../serialapi/capability/LongRangeChannelMessages"; +} from "@zwave-js/serial/serialapi"; import { SerialAPISetupCommand, SerialAPISetup_CommandUnsupportedResponse, @@ -230,28 +199,28 @@ import { type SerialAPISetup_SetRFRegionResponse, SerialAPISetup_SetTXStatusReportRequest, type SerialAPISetup_SetTXStatusReportResponse, -} from "../serialapi/capability/SerialAPISetupMessages"; -import { SetApplicationNodeInformationRequest } from "../serialapi/capability/SetApplicationNodeInformationRequest"; +} from "@zwave-js/serial/serialapi"; +import { SetApplicationNodeInformationRequest } from "@zwave-js/serial/serialapi"; import { GetControllerIdRequest, type GetControllerIdResponse, -} from "../serialapi/memory/GetControllerIdMessages"; +} from "@zwave-js/serial/serialapi"; import { GetBackgroundRSSIRequest, type GetBackgroundRSSIResponse, -} from "../serialapi/misc/GetBackgroundRSSIMessages"; +} from "@zwave-js/serial/serialapi"; import { SetRFReceiveModeRequest, type SetRFReceiveModeResponse, -} from "../serialapi/misc/SetRFReceiveModeMessages"; +} from "@zwave-js/serial/serialapi"; import { SetSerialApiTimeoutsRequest, type SetSerialApiTimeoutsResponse, -} from "../serialapi/misc/SetSerialApiTimeoutsMessages"; +} from "@zwave-js/serial/serialapi"; import { StartWatchdogRequest, StopWatchdogRequest, -} from "../serialapi/misc/WatchdogMessages"; +} from "@zwave-js/serial/serialapi"; import { AddNodeDSKToNetworkRequest, AddNodeStatus, @@ -260,93 +229,93 @@ import { AddNodeType, EnableSmartStartListenRequest, computeNeighborDiscoveryTimeout, -} from "../serialapi/network-mgmt/AddNodeToNetworkRequest"; -import { AssignPriorityReturnRouteRequest } from "../serialapi/network-mgmt/AssignPriorityReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; +import { AssignPriorityReturnRouteRequest } from "@zwave-js/serial/serialapi"; import { AssignPrioritySUCReturnRouteRequest, type AssignPrioritySUCReturnRouteRequestTransmitReport, -} from "../serialapi/network-mgmt/AssignPrioritySUCReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { AssignReturnRouteRequest, type AssignReturnRouteRequestTransmitReport, -} from "../serialapi/network-mgmt/AssignReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { AssignSUCReturnRouteRequest, AssignSUCReturnRouteRequestTransmitReport, -} from "../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { DeleteReturnRouteRequest, type DeleteReturnRouteRequestTransmitReport, -} from "../serialapi/network-mgmt/DeleteReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { DeleteSUCReturnRouteRequest, DeleteSUCReturnRouteRequestTransmitReport, -} from "../serialapi/network-mgmt/DeleteSUCReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { GetPriorityRouteRequest, type GetPriorityRouteResponse, -} from "../serialapi/network-mgmt/GetPriorityRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { GetRoutingInfoRequest, type GetRoutingInfoResponse, -} from "../serialapi/network-mgmt/GetRoutingInfoMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSUCNodeIdRequest, type GetSUCNodeIdResponse, -} from "../serialapi/network-mgmt/GetSUCNodeIdMessages"; +} from "@zwave-js/serial/serialapi"; import { IsFailedNodeRequest, type IsFailedNodeResponse, -} from "../serialapi/network-mgmt/IsFailedNodeMessages"; +} from "@zwave-js/serial/serialapi"; import { RemoveFailedNodeRequest, type RemoveFailedNodeRequestStatusReport, RemoveFailedNodeResponse, RemoveFailedNodeStartFlags, RemoveFailedNodeStatus, -} from "../serialapi/network-mgmt/RemoveFailedNodeMessages"; +} from "@zwave-js/serial/serialapi"; import { RemoveNodeFromNetworkRequest, type RemoveNodeFromNetworkRequestStatusReport, RemoveNodeStatus, RemoveNodeType, -} from "../serialapi/network-mgmt/RemoveNodeFromNetworkRequest"; +} from "@zwave-js/serial/serialapi"; import { ReplaceFailedNodeRequest, type ReplaceFailedNodeRequestStatusReport, type ReplaceFailedNodeResponse, ReplaceFailedNodeStartFlags, ReplaceFailedNodeStatus, -} from "../serialapi/network-mgmt/ReplaceFailedNodeRequest"; +} from "@zwave-js/serial/serialapi"; import { NodeNeighborUpdateStatus, type RequestNodeNeighborUpdateReport, RequestNodeNeighborUpdateRequest, -} from "../serialapi/network-mgmt/RequestNodeNeighborUpdateMessages"; +} from "@zwave-js/serial/serialapi"; import { LearnModeIntent, LearnModeStatus, type SetLearnModeCallback, SetLearnModeRequest, -} from "../serialapi/network-mgmt/SetLearnModeMessages"; -import { SetPriorityRouteRequest } from "../serialapi/network-mgmt/SetPriorityRouteMessages"; -import { SetSUCNodeIdRequest } from "../serialapi/network-mgmt/SetSUCNodeIDMessages"; +} from "@zwave-js/serial/serialapi"; +import { SetPriorityRouteRequest } from "@zwave-js/serial/serialapi"; +import { SetSUCNodeIdRequest } from "@zwave-js/serial/serialapi"; import { ExtNVMReadLongBufferRequest, type ExtNVMReadLongBufferResponse, -} from "../serialapi/nvm/ExtNVMReadLongBufferMessages"; +} from "@zwave-js/serial/serialapi"; import { ExtNVMReadLongByteRequest, type ExtNVMReadLongByteResponse, -} from "../serialapi/nvm/ExtNVMReadLongByteMessages"; +} from "@zwave-js/serial/serialapi"; import { ExtNVMWriteLongBufferRequest, type ExtNVMWriteLongBufferResponse, -} from "../serialapi/nvm/ExtNVMWriteLongBufferMessages"; +} from "@zwave-js/serial/serialapi"; import { ExtNVMWriteLongByteRequest, type ExtNVMWriteLongByteResponse, -} from "../serialapi/nvm/ExtNVMWriteLongByteMessages"; +} from "@zwave-js/serial/serialapi"; import { ExtendedNVMOperationStatus, ExtendedNVMOperationsCloseRequest, @@ -355,7 +324,7 @@ import { ExtendedNVMOperationsReadRequest, type ExtendedNVMOperationsResponse, ExtendedNVMOperationsWriteRequest, -} from "../serialapi/nvm/ExtendedNVMOperationsMessages"; +} from "@zwave-js/serial/serialapi"; import { FirmwareUpdateNVM_GetNewImageRequest, type FirmwareUpdateNVM_GetNewImageResponse, @@ -369,13 +338,13 @@ import { type FirmwareUpdateNVM_UpdateCRC16Response, FirmwareUpdateNVM_WriteRequest, type FirmwareUpdateNVM_WriteResponse, -} from "../serialapi/nvm/FirmwareUpdateNVMMessages"; +} from "@zwave-js/serial/serialapi"; import { GetNVMIdRequest, type GetNVMIdResponse, type NVMId, nvmSizeToBufferSize, -} from "../serialapi/nvm/GetNVMIdMessages"; +} from "@zwave-js/serial/serialapi"; import { NVMOperationStatus, NVMOperationsCloseRequest, @@ -383,8 +352,44 @@ import { NVMOperationsReadRequest, type NVMOperationsResponse, NVMOperationsWriteRequest, -} from "../serialapi/nvm/NVMOperationsMessages"; -import type { TransmitReport } from "../serialapi/transport/SendDataShared"; +} from "@zwave-js/serial/serialapi"; +import type { TransmitReport } from "@zwave-js/serial/serialapi"; +import { + Mixin, + type ReadonlyObjectKeyMap, + type ReadonlyThrowingMap, + type ThrowingMap, + TypedEventEmitter, + cloneDeep, + createThrowingMap, + flatMap, + getEnumMemberName, + getErrorMessage, + noop, + num2hex, + pick, +} 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 { isObject } from "alcalzone-shared/typeguards"; +import crypto from "node:crypto"; +import type { Driver } from "../driver/Driver"; +import { cacheKeyUtils, cacheKeys } from "../driver/NetworkCache"; +import type { StatisticsEventCallbacks } from "../driver/Statistics"; +import { type TaskBuilder, TaskPriority } from "../driver/Task"; +import { DeviceClass } from "../node/DeviceClass"; +import { ZWaveNode } from "../node/Node"; +import { VirtualNode } from "../node/VirtualNode"; +import { + InterviewStage, + type LifelineRoutes, + NodeStatus, +} from "../node/_Types"; import { type ControllerStatistics, ControllerStatisticsHost, @@ -431,14 +436,7 @@ import { type RebuildRoutesStatus, type SDKVersion, } from "./_Types"; -import { - assertProvisioningEntry, - isRebuildRoutesTask, - sdkVersionGt, - sdkVersionGte, - sdkVersionLt, - sdkVersionLte, -} from "./utils"; +import { assertProvisioningEntry, isRebuildRoutesTask } from "./utils"; // Strongly type the event emitter events interface ControllerEventCallbacks @@ -1144,7 +1142,7 @@ export class ZWaveController const apiCaps = await this.driver.sendMessage< GetSerialApiCapabilitiesResponse >( - new GetSerialApiCapabilitiesRequest(this.driver), + new GetSerialApiCapabilitiesRequest(), { supportCheck: false, }, @@ -1175,7 +1173,7 @@ export class ZWaveController const version = await this.driver.sendMessage< GetControllerVersionResponse >( - new GetControllerVersionRequest(this.driver), + new GetControllerVersionRequest(), { supportCheck: false, }, @@ -1196,7 +1194,7 @@ export class ZWaveController const protocol = await this.driver.sendMessage< GetProtocolVersionResponse >( - new GetProtocolVersionRequest(this.driver), + new GetProtocolVersionRequest(), ); this._protocolVersion = protocol.protocolVersion; @@ -1236,7 +1234,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 +1687,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 +1714,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< SerialAPISetup_SetTXStatusReportResponse >( - new SerialAPISetup_SetTXStatusReportRequest(this.driver, { + new SerialAPISetup_SetTXStatusReportRequest({ enabled: true, }), ); @@ -1730,7 +1728,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 +1790,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< SetSerialApiTimeoutsResponse >( - new SetSerialApiTimeoutsRequest(this.driver, { + new SetSerialApiTimeoutsRequest({ ackTimeout: ack, byteTimeout: byte, }), @@ -1926,7 +1924,7 @@ export class ZWaveController const nodesResponse = await this.driver.sendMessage< GetLongRangeNodesResponse >( - new GetLongRangeNodesRequest(this.driver, { + new GetLongRangeNodesRequest({ segmentNumber: segment, }), ); @@ -1946,7 +1944,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 +1976,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 +2001,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 +2034,7 @@ export class ZWaveController "Starting hardware watchdog...", ); await this.driver.sendMessage( - new StartWatchdogRequest(this.driver), + new StartWatchdogRequest(), ); return true; @@ -2063,7 +2061,7 @@ export class ZWaveController "Stopping hardware watchdog...", ); await this.driver.sendMessage( - new StopWatchdogRequest(this.driver), + new StopWatchdogRequest(), ); return true; @@ -2159,7 +2157,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 +2228,7 @@ export class ZWaveController ); await this.driver.sendMessage( - new AddNodeDSKToNetworkRequest(this.driver, { + new AddNodeDSKToNetworkRequest({ nwiHomeId: nwiHomeIdFromDSK(dskBuffer), authHomeId: authHomeIdFromDSK(dskBuffer), protocol, @@ -2255,7 +2253,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 +2274,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 +2308,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 +2362,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 +2406,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 +2448,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 +2503,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 +2540,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 +2565,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 +5109,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 +5165,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 +5287,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 +5315,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 +5370,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 +5491,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignReturnRouteRequestTransmitReport >( - new AssignReturnRouteRequest(this.driver, { + new AssignReturnRouteRequest({ nodeId, destinationNodeId, }), @@ -5585,7 +5597,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 +5625,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 +5701,7 @@ export class ZWaveController const result = await this.driver.sendMessage< DeleteReturnRouteRequestTransmitReport >( - new DeleteReturnRouteRequest(this.driver, { + new DeleteReturnRouteRequest({ nodeId, }), ); @@ -5748,7 +5757,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignReturnRouteRequestTransmitReport >( - new AssignPriorityReturnRouteRequest(this.driver, { + new AssignPriorityReturnRouteRequest({ nodeId, destinationNodeId, repeaters, @@ -5865,7 +5874,7 @@ export class ZWaveController const result = await this.driver.sendMessage< AssignPrioritySUCReturnRouteRequestTransmitReport >( - new AssignPrioritySUCReturnRouteRequest(this.driver, { + new AssignPrioritySUCReturnRouteRequest({ nodeId, repeaters, routeSpeed, @@ -5970,7 +5979,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetPriorityRouteRequest(this.driver, { + new SetPriorityRouteRequest({ destinationNodeId, repeaters, routeSpeed, @@ -6007,7 +6016,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetPriorityRouteRequest(this.driver, { + new SetPriorityRouteRequest({ destinationNodeId, // no repeaters = remove }), @@ -6049,7 +6058,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetPriorityRouteResponse >( - new GetPriorityRouteRequest(this.driver, { + new GetPriorityRouteRequest({ destinationNodeId, }), ); @@ -6301,7 +6310,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 +6355,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 +6464,7 @@ export class ZWaveController this._inclusionOptions = options; const result = await this.driver.sendMessage( - new ReplaceFailedNodeRequest(this.driver, { + new ReplaceFailedNodeRequest({ failedNodeId: nodeId, }), ); @@ -6528,7 +6537,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 +6555,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 +6575,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 +6601,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 +6691,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 +6730,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 +6753,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 +6777,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 +6816,7 @@ export class ZWaveController const result = await this.driver.sendMessage< SetLongRangeChannelResponse >( - new SetLongRangeChannelRequest( - this.driver, - { channel }, - ), + new SetLongRangeChannelRequest({ channel }), ); if (result.success) { @@ -6832,7 +6832,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetLongRangeChannelResponse >( - new GetLongRangeChannelRequest(this.driver), + new GetLongRangeChannelRequest(), ); const channel = result.autoChannelSelectionActive @@ -6865,7 +6865,7 @@ export class ZWaveController | SerialAPISetup_SetNodeIDTypeResponse | SerialAPISetup_CommandUnsupportedResponse >( - new SerialAPISetup_SetNodeIDTypeRequest(this.driver, { + new SerialAPISetup_SetNodeIDTypeRequest({ nodeIdType, }), ); @@ -6911,7 +6911,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 +6932,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 +6975,7 @@ export class ZWaveController const resp = await this.driver.sendMessage< RequestNodeNeighborUpdateReport >( - new RequestNodeNeighborUpdateRequest(this.driver, { + new RequestNodeNeighborUpdateRequest({ nodeId, discoveryTimeout, }), @@ -7016,7 +7014,7 @@ export class ZWaveController }); try { const resp = await this.driver.sendMessage( - new GetRoutingInfoRequest(this.driver, { + new GetRoutingInfoRequest({ nodeId, removeBadLinks: false, removeNonRepeaters: onlyRepeaters, @@ -7065,7 +7063,7 @@ export class ZWaveController const initData = await this.driver.sendMessage< GetSerialApiInitDataResponse >( - new GetSerialApiInitDataRequest(this.driver), + new GetSerialApiInitDataRequest(), ); this.driver.controllerLog.print( @@ -7121,7 +7119,7 @@ export class ZWaveController const result = await this.driver.sendMessage< GetControllerCapabilitiesResponse >( - new GetControllerCapabilitiesRequest(this.driver), + new GetControllerCapabilitiesRequest(), { supportCheck: false }, ); @@ -7183,7 +7181,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 +7224,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 +7238,7 @@ export class ZWaveController value: boolean = true, ): Promise { await this.driver.sendMessage( - new FirmwareUpdateNVM_SetNewImageRequest(this.driver, { + new FirmwareUpdateNVM_SetNewImageRequest({ newImage: value, }), ); @@ -7255,7 +7253,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 +7271,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 +7290,7 @@ export class ZWaveController buffer: Buffer, ): Promise { await this.driver.sendMessage( - new FirmwareUpdateNVM_WriteRequest(this.driver, { + new FirmwareUpdateNVM_WriteRequest({ offset, buffer, }), @@ -7308,7 +7306,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 +7318,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 +7330,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 +7349,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 +7364,7 @@ export class ZWaveController length: number, ): Promise { const ret = await this.driver.sendMessage( - new ExtNVMReadLongBufferRequest(this.driver, { + new ExtNVMReadLongBufferRequest({ offset, length, }), @@ -7386,7 +7384,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 +7424,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsReadRequest(this.driver, { + new ExtendedNVMOperationsReadRequest({ offset, length, }), @@ -7472,7 +7470,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtNVMWriteLongBufferResponse >( - new ExtNVMWriteLongBufferRequest(this.driver, { + new ExtNVMWriteLongBufferRequest({ offset, buffer, }), @@ -7495,7 +7493,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 +7536,7 @@ export class ZWaveController const ret = await this.driver.sendMessage< ExtendedNVMOperationsResponse >( - new ExtendedNVMOperationsWriteRequest(this.driver, { + new ExtendedNVMOperationsWriteRequest({ offset, buffer, }), @@ -7582,7 +7580,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 +7605,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 +7633,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 +7654,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 +8088,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 +8808,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ intent: LearnModeIntent.Inclusion, }), ); @@ -8844,7 +8842,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 +8878,7 @@ export class ZWaveController const result = await this.driver.sendMessage< Message & SuccessIndicator >( - new SetLearnModeRequest(this.driver, { + new SetLearnModeRequest({ intent: LearnModeIntent.NetworkWideExclusion, }), ); @@ -8916,7 +8914,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 +9673,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 010bbab67471..d46ad7394fd1 100644 --- a/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts +++ b/packages/zwave-js/src/lib/controller/MockControllerBehaviors.ts @@ -13,77 +13,66 @@ 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, - type MockController, - type MockControllerBehavior, - type MockNode, - MockZWaveFrameType, - createMockZWaveRequestFrame, -} from "@zwave-js/testing"; -import { wait } from "alcalzone-shared/async"; -import { ApplicationCommandRequest } from "../serialapi/application/ApplicationCommandRequest"; +import { ApplicationCommandRequest } from "@zwave-js/serial/serialapi"; import { type ApplicationUpdateRequest, ApplicationUpdateRequestNodeInfoReceived, ApplicationUpdateRequestNodeInfoRequestFailed, -} from "../serialapi/application/ApplicationUpdateRequest"; +} from "@zwave-js/serial/serialapi"; import { SerialAPIStartedRequest, SerialAPIWakeUpReason, -} from "../serialapi/application/SerialAPIStartedRequest"; +} from "@zwave-js/serial/serialapi"; import { GetControllerCapabilitiesRequest, GetControllerCapabilitiesResponse, -} from "../serialapi/capability/GetControllerCapabilitiesMessages"; +} from "@zwave-js/serial/serialapi"; import { GetControllerVersionRequest, GetControllerVersionResponse, -} from "../serialapi/capability/GetControllerVersionMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSerialApiCapabilitiesRequest, GetSerialApiCapabilitiesResponse, -} from "../serialapi/capability/GetSerialApiCapabilitiesMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSerialApiInitDataRequest, GetSerialApiInitDataResponse, -} from "../serialapi/capability/GetSerialApiInitDataMessages"; +} from "@zwave-js/serial/serialapi"; import { GetControllerIdRequest, GetControllerIdResponse, -} from "../serialapi/memory/GetControllerIdMessages"; -import { SoftResetRequest } from "../serialapi/misc/SoftResetRequest"; +} from "@zwave-js/serial/serialapi"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; import { AddNodeStatus, AddNodeToNetworkRequest, AddNodeToNetworkRequestStatusReport, AddNodeType, -} from "../serialapi/network-mgmt/AddNodeToNetworkRequest"; +} from "@zwave-js/serial/serialapi"; import { AssignSUCReturnRouteRequest, AssignSUCReturnRouteRequestTransmitReport, AssignSUCReturnRouteResponse, -} from "../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; +} from "@zwave-js/serial/serialapi"; import { GetNodeProtocolInfoRequest, GetNodeProtocolInfoResponse, -} from "../serialapi/network-mgmt/GetNodeProtocolInfoMessages"; +} from "@zwave-js/serial/serialapi"; import { GetSUCNodeIdRequest, GetSUCNodeIdResponse, -} from "../serialapi/network-mgmt/GetSUCNodeIdMessages"; +} from "@zwave-js/serial/serialapi"; import { RemoveNodeFromNetworkRequest, RemoveNodeFromNetworkRequestStatusReport, RemoveNodeStatus, RemoveNodeType, -} from "../serialapi/network-mgmt/RemoveNodeFromNetworkRequest"; +} from "@zwave-js/serial/serialapi"; import { RequestNodeInfoRequest, RequestNodeInfoResponse, -} from "../serialapi/network-mgmt/RequestNodeInfoMessages"; +} from "@zwave-js/serial/serialapi"; import { SendDataMulticastRequest, SendDataMulticastRequestTransmitReport, @@ -91,7 +80,15 @@ import { SendDataRequest, SendDataRequestTransmitReport, SendDataResponse, -} from "../serialapi/transport/SendDataMessages"; +} from "@zwave-js/serial/serialapi"; +import { + MOCK_FRAME_ACK_TIMEOUT, + type MockController, + type MockControllerBehavior, + type MockNode, + MockZWaveFrameType, + createMockZWaveRequestFrame, +} from "@zwave-js/testing"; import { MockControllerCommunicationState, MockControllerInclusionState, @@ -100,17 +97,19 @@ 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, - data: msg.payload, - origin: MessageOrigin.Host, + const cmd = CommandClass.parse(msg.serializedCC!, { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...node.encodingContext, + ...node.securityManagers, + // The frame type is always singlecast because the controller sends it to the node + frameType: "singlecast", }); // Store the command because assertReceivedHostMessage needs it // @ts-expect-error @@ -121,8 +120,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), @@ -151,76 +150,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, @@ -230,16 +229,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, @@ -247,7 +246,7 @@ const respondToSoftReset: MockControllerBehavior = { supportsLongRange: controller.capabilities.supportsLongRange, }); setImmediate(async () => { - await controller.sendToHost(ret.serialize()); + await controller.sendMessageToHost(ret); }); return true; } @@ -255,10 +254,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, @@ -270,16 +269,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; } } @@ -287,7 +286,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( @@ -307,10 +306,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, @@ -318,7 +317,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, @@ -358,14 +356,14 @@ const handleSendData: MockControllerBehavior = { MockControllerCommunicationState.Idle, ); - const cb = new SendDataRequestTransmitReport(host, { + 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( @@ -379,7 +377,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( @@ -401,21 +399,20 @@ 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, // so CC knows it came from the controller's node ID. - const nodeIds = msg["_nodeIds"]!; + const nodeIds = msg.nodeIds; const ackPromises = nodeIds.map((nodeId) => { 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, @@ -457,14 +454,14 @@ const handleSendDataMulticast: MockControllerBehavior = { MockControllerCommunicationState.Idle, ); - const cb = new SendDataMulticastRequestTransmitReport(host, { + 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( @@ -478,7 +475,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( @@ -501,10 +498,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, }); @@ -516,10 +512,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( @@ -531,28 +527,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( @@ -577,9 +573,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"], @@ -591,10 +587,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) { @@ -618,14 +614,14 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { ); if (expectCallback) { - const cb = new AssignSUCReturnRouteRequestTransmitReport(host, { + const cb = new AssignSUCReturnRouteRequestTransmitReport({ callbackId: msg.callbackId, transmitStatus: ack ? TransmitStatus.OK : TransmitStatus.NoAck, }); - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } return true; } @@ -633,7 +629,7 @@ const handleAssignSUCReturnRoute: MockControllerBehavior = { }; const handleAddNode: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof AddNodeToNetworkRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -651,31 +647,22 @@ const handleAddNode: MockControllerBehavior = { MockControllerStateKeys.InclusionState, MockControllerInclusionState.Idle, ); - cb = new AddNodeToNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: AddNodeStatus.Failed, - }, - ); + cb = new AddNodeToNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: AddNodeStatus.Failed, + }); } else { - cb = new AddNodeToNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: AddNodeStatus.Failed, - }, - ); + cb = new AddNodeToNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: AddNodeStatus.Failed, + }); } } else if (state === MockControllerInclusionState.RemovingNode) { // Cannot start adding nodes while removing one - cb = new AddNodeToNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: AddNodeStatus.Failed, - }, - ); + cb = new AddNodeToNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: AddNodeStatus.Failed, + }); } else { // Idle @@ -686,17 +673,14 @@ const handleAddNode: MockControllerBehavior = { MockControllerInclusionState.AddingNode, ); - cb = new AddNodeToNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: AddNodeStatus.Ready, - }, - ); + cb = new AddNodeToNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: AddNodeStatus.Ready, + }); } if (expectCallback && cb) { - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } return true; @@ -705,7 +689,7 @@ const handleAddNode: MockControllerBehavior = { }; const handleRemoveNode: MockControllerBehavior = { - async onHostMessage(host, controller, msg) { + async onHostMessage(controller, msg) { if (msg instanceof RemoveNodeFromNetworkRequest) { // Check if this command is legal right now const state = controller.state.get( @@ -723,31 +707,22 @@ const handleRemoveNode: MockControllerBehavior = { MockControllerStateKeys.InclusionState, MockControllerInclusionState.Idle, ); - cb = new RemoveNodeFromNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: RemoveNodeStatus.Failed, - }, - ); + cb = new RemoveNodeFromNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: RemoveNodeStatus.Failed, + }); } else { - cb = new RemoveNodeFromNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: RemoveNodeStatus.Failed, - }, - ); + cb = new RemoveNodeFromNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: RemoveNodeStatus.Failed, + }); } } else if (state === MockControllerInclusionState.AddingNode) { // Cannot start removing nodes while adding one - cb = new RemoveNodeFromNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: RemoveNodeStatus.Failed, - }, - ); + cb = new RemoveNodeFromNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: RemoveNodeStatus.Failed, + }); } else { // Idle @@ -758,17 +733,14 @@ const handleRemoveNode: MockControllerBehavior = { MockControllerInclusionState.RemovingNode, ); - cb = new RemoveNodeFromNetworkRequestStatusReport( - host, - { - callbackId: msg.callbackId, - status: RemoveNodeStatus.Ready, - }, - ); + cb = new RemoveNodeFromNetworkRequestStatusReport({ + callbackId: msg.callbackId, + status: RemoveNodeStatus.Ready, + }); } if (expectCallback && cb) { - await controller.sendToHost(cb.serialize()); + await controller.sendMessageToHost(cb); } return true; @@ -777,48 +749,42 @@ const handleRemoveNode: 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/controller/NVMIO.ts b/packages/zwave-js/src/lib/controller/NVMIO.ts index 07ebc78e2c91..cdee587e9a6c 100644 --- a/packages/zwave-js/src/lib/controller/NVMIO.ts +++ b/packages/zwave-js/src/lib/controller/NVMIO.ts @@ -1,7 +1,7 @@ import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; import { NVMAccess, type NVMIO } from "@zwave-js/nvmedit"; import { FunctionType } from "@zwave-js/serial"; -import { nvmSizeToBufferSize } from "../serialapi/nvm/GetNVMIdMessages"; +import { nvmSizeToBufferSize } from "@zwave-js/serial/serialapi"; import { type ZWaveController } from "./Controller"; /** NVM IO over serial for 500 series controllers */ diff --git a/packages/zwave-js/src/lib/controller/utils.ts b/packages/zwave-js/src/lib/controller/utils.ts index 8d5655061c3c..5d14963948db 100644 --- a/packages/zwave-js/src/lib/controller/utils.ts +++ b/packages/zwave-js/src/lib/controller/utils.ts @@ -1,14 +1,11 @@ import { - type MaybeNotKnown, Protocols, SecurityClass, ZWaveError, ZWaveErrorCodes, isValidDSK, } from "@zwave-js/core/safe"; -import { padVersion } from "@zwave-js/shared/safe"; import { isArray, isObject } from "alcalzone-shared/typeguards"; -import semver from "semver"; import { type Task } from "../driver/Task"; import { type PlannedProvisioningEntry, @@ -84,50 +81,6 @@ export function assertProvisioningEntry( } } -/** Checks if the SDK version is greater than the given one */ -export function sdkVersionGt( - sdkVersion: MaybeNotKnown, - compareVersion: string, -): MaybeNotKnown { - if (sdkVersion === undefined) { - return undefined; - } - return semver.gt(padVersion(sdkVersion), padVersion(compareVersion)); -} - -/** Checks if the SDK version is greater than or equal to the given one */ -export function sdkVersionGte( - sdkVersion: MaybeNotKnown, - compareVersion: string, -): MaybeNotKnown { - if (sdkVersion === undefined) { - return undefined; - } - return semver.gte(padVersion(sdkVersion), padVersion(compareVersion)); -} - -/** Checks if the SDK version is lower than the given one */ -export function sdkVersionLt( - sdkVersion: MaybeNotKnown, - compareVersion: string, -): MaybeNotKnown { - if (sdkVersion === undefined) { - return undefined; - } - return semver.lt(padVersion(sdkVersion), padVersion(compareVersion)); -} - -/** Checks if the SDK version is lower than or equal to the given one */ -export function sdkVersionLte( - sdkVersion: MaybeNotKnown, - compareVersion: string, -): MaybeNotKnown { - if (sdkVersion === undefined) { - return undefined; - } - return semver.lte(padVersion(sdkVersion), padVersion(compareVersion)); -} - /** Checks if a task belongs to a route rebuilding process */ export function isRebuildRoutesTask(t: Task): boolean { return t.tag?.id === "rebuild-routes" diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 947deff90ca8..31e66af87805 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, + CommandClass, type FirmwareUpdateResult, - type ICommandClassContainer, + type InterviewContext, InvalidCC, KEXFailType, MultiChannelCC, + NoOperationCC, + type PersistValuesContext, type Powerlevel, + type RefreshValuesContext, Security2CC, Security2CCCommandsSupportedGet, Security2CCCommandsSupportedReport, @@ -35,13 +39,10 @@ import { WakeUpCCNoMoreInformation, WakeUpCCValues, type ZWaveProtocolCC, - assertValidCCs, getImplementedVersion, - isCommandClassContainer, isEncapsulatingCommandClass, isMultiEncapsulatingCommandClass, isTransportServiceEncapsulation, - messageIsPing, } from "@zwave-js/cc"; import { ConfigManager, @@ -49,15 +50,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,16 +111,21 @@ import { wasControllerReset, } from "@zwave-js/core"; import type { + CCEncodingContext, + CCParsingContext, + HostIDs, NodeSchedulePollOptions, - ZWaveApplicationHost, + ZWaveHostOptions, } from "@zwave-js/host"; import { type BootloaderChunk, BootloaderChunkType, FunctionType, - type INodeQuery, + type HasNodeId, Message, + type MessageEncodingContext, MessageHeaders, + type MessageParsingContext, MessageType, type SuccessIndicator, XModemMessageHeaders, @@ -128,13 +135,37 @@ import { type ZWaveSerialPortImplementation, ZWaveSocket, getDefaultPriority, - isNodeQuery, + hasNodeId, isSuccessIndicator, isZWaveSerialPortImplementation, } from "@zwave-js/serial"; +import { ApplicationUpdateRequest } from "@zwave-js/serial/serialapi"; +import { + SerialAPIStartedRequest, + SerialAPIWakeUpReason, +} from "@zwave-js/serial/serialapi"; +import { GetControllerVersionRequest } from "@zwave-js/serial/serialapi"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; +import { + SendDataBridgeRequest, + SendDataMulticastBridgeRequest, +} from "@zwave-js/serial/serialapi"; +import { + MAX_SEND_ATTEMPTS, + SendDataAbort, + SendDataMulticastRequest, + SendDataRequest, +} from "@zwave-js/serial/serialapi"; +import { + type SendDataMessage, + hasTXReport, + isSendData, + isSendDataSinglecast, + isSendDataTransmitReport, + isTransmitReport, +} from "@zwave-js/serial/serialapi"; import { AsyncQueue, - type ReadonlyThrowingMap, type ThrowingMap, TypedEventEmitter, buffer2hex, @@ -175,38 +206,22 @@ import { type ZWaveNotificationCallback, zWaveNodeEvents, } from "../node/_Types"; -import { ApplicationCommandRequest } from "../serialapi/application/ApplicationCommandRequest"; -import { ApplicationUpdateRequest } from "../serialapi/application/ApplicationUpdateRequest"; -import { BridgeApplicationCommandRequest } from "../serialapi/application/BridgeApplicationCommandRequest"; -import { - SerialAPIStartedRequest, - SerialAPIWakeUpReason, -} from "../serialapi/application/SerialAPIStartedRequest"; -import { GetControllerVersionRequest } from "../serialapi/capability/GetControllerVersionMessages"; -import { SoftResetRequest } from "../serialapi/misc/SoftResetRequest"; -import { - SendDataBridgeRequest, - SendDataMulticastBridgeRequest, -} from "../serialapi/transport/SendDataBridgeMessages"; -import { - MAX_SEND_ATTEMPTS, - SendDataAbort, - SendDataMulticastRequest, - SendDataRequest, -} from "../serialapi/transport/SendDataMessages"; -import { - type SendDataMessage, - hasTXReport, - isSendData, - isSendDataSinglecast, - isSendDataTransmitReport, - isTransmitReport, -} from "../serialapi/transport/SendDataShared"; import { SendTestFrameRequest, SendTestFrameTransmitReport, -} from "../serialapi/transport/SendTestFrameMessages"; +} from "@zwave-js/serial/serialapi"; +import { + type CommandRequest, + type ContainsCC, + containsCC, + containsSerializedCC, + isCommandRequest, +} from "@zwave-js/serial/serialapi"; +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 { reportMissingDeviceConfig } from "../telemetry/deviceConfig"; import { type AppInfo, @@ -539,7 +554,7 @@ interface AwaitedThing { type AwaitedMessageHeader = AwaitedThing; type AwaitedMessageEntry = AwaitedThing; -type AwaitedCommandEntry = AwaitedThing; +type AwaitedCommandEntry = AwaitedThing; export type AwaitedBootloaderChunkEntry = AwaitedThing; interface TransportServiceSession { @@ -572,6 +587,31 @@ const enum ControllerRecoveryPhase { JammedAfterReset, } +function messageIsPing( + msg: T, +): msg is T & ContainsCC { + return containsCC(msg) && msg.command instanceof NoOperationCC; +} + +function assertValidCCs(container: ContainsCC): void { + if (container.command instanceof InvalidCC) { + if (typeof container.command.reason === "number") { + throw new ZWaveError( + "The message payload failed validation!", + container.command.reason, + ); + } else { + throw new ZWaveError( + "The message payload is invalid!", + ZWaveErrorCodes.PacketFormat_InvalidPayload, + container.command.reason, + ); + } + } else if (containsCC(container.command)) { + assertValidCCs(container.command); + } +} + // Strongly type the event emitter events export interface DriverEventCallbacks extends PrefixedNodeEvents { "driver ready": () => void; @@ -589,7 +629,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 +694,29 @@ export class Driver extends TypedEventEmitter this._options.storage.deviceConfigPriorityDir, }); + const self = this; + this.messageEncodingContext = { + 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), + // These are evaluated lazily, so we cannot spread messageParsingContext unfortunately + get securityManager() { + return self.securityManager; + }, + get securityManager2() { + return self.securityManager2; + }, + get securityManagerLR() { + return self.securityManagerLR; + }, + getSupportedCCVersion: (cc, nodeId, endpointIndex) => + this.getSupportedCCVersion(cc, nodeId, endpointIndex), + }; + this.immediateQueue = new TransactionQueue({ name: "immediate", mayStartNextTransaction: (t) => { @@ -684,6 +751,42 @@ export class Driver extends TypedEventEmitter /** The serial port instance */ private serial: ZWaveSerialPortBase | undefined; + private messageEncodingContext: Omit< + MessageEncodingContext, + keyof HostIDs | "nodeIdType" + >; + + private getEncodingContext(): MessageEncodingContext & CCEncodingContext { + return { + ...this.messageEncodingContext, + ownNodeId: this.controller.ownNodeId!, + homeId: this.controller.homeId!, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, + }; + } + + private getMessageParsingContext(): MessageParsingContext { + return { + getDeviceConfig: (nodeId) => this.getDeviceConfig(nodeId), + sdkVersion: this._controller?.sdkVersion, + requestStorage: this._requestStorage, + ownNodeId: this.controller.ownNodeId!, + homeId: this.controller.homeId!, + nodeIdType: this._controller?.nodeIdType ?? NodeIDType.Short, + }; + } + + private getCCParsingContext(): Omit< + CCParsingContext, + "sourceNodeId" | "frameType" + > { + return { + ...this.messageEncodingContext, + ownNodeId: this.controller.ownNodeId!, + homeId: this.controller.homeId!, + }; + } + // We have multiple queues to achieve multiple "layers" of communication priority: // The default queue for most messages private queue: TransactionQueue; @@ -746,14 +849,14 @@ export class Driver extends TypedEventEmitter return this.nodeSessions.get(nodeId)!; } - private _requestContext: Map> = + private _requestStorage: Map> = new Map(); /** * @internal * Stores data from Serial API command requests to be used by their responses */ - public get requestContext(): Map> { - return this._requestContext; + public get requestStorage(): Map> { + return this._requestStorage; } public readonly cacheDir: string; @@ -797,15 +900,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 { @@ -922,21 +1032,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); } - public getNodeUnsafe(msg: Message): ZWaveNode | undefined { + /** @internal Used for compatibility with the CCAPIHost interface */ + public getAllNodes(): ZWaveNode[] { + return [...this.controller.nodes.values()]; + } + + public tryGetNode(msg: Message): ZWaveNode | undefined { const nodeId = msg.getNodeId(); if (nodeId != undefined) return this.controller.nodes.get(nodeId); } @@ -976,6 +1087,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 { @@ -1008,16 +1123,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); @@ -1038,6 +1143,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 @@ -2689,7 +2821,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( @@ -2700,7 +2832,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 */ @@ -2743,7 +2875,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 @@ -2753,7 +2885,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, @@ -2761,28 +2900,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); } /** @@ -2850,14 +2971,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); } @@ -2952,7 +3073,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, }); @@ -3012,7 +3133,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, }); @@ -3104,7 +3225,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, }); @@ -3413,22 +3534,30 @@ 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, { - data, - sdkVersion: this._controller?.sdkVersion, - }, this._requestContext); - if (isCommandClassContainer(msg)) { + msg = Message.parse(data, this.getMessageParsingContext()); + + // Parse embedded CCs + if (isCommandRequest(msg) && containsSerializedCC(msg)) { + msg.command = CommandClass.parse( + msg.serializedCC, + { + ...this.getCCParsingContext(), + sourceNodeId: msg.getNodeId()!, + frameType: msg.frameType, + }, + ); + // 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 - assertValidCCs(msg); + assertValidCCs(msg as ContainsCC); } // And update statistics if (!!this._controller) { - if (isCommandClassContainer(msg)) { - this.getNodeUnsafe(msg)?.incrementStatistics("commandsRX"); + if (containsCC(msg)) { + this.tryGetNode(msg)?.incrementStatistics("commandsRX"); } else { this._controller.incrementStatistics("messagesRX"); } @@ -3443,8 +3572,8 @@ export class Driver extends TypedEventEmitter const response = this.handleDecodeError(e, data, msg); if (response) await this.writeHeader(response); if (!!this._controller) { - if (isCommandClassContainer(msg)) { - this.getNodeUnsafe(msg)?.incrementStatistics( + if (containsCC(msg)) { + this.tryGetNode(msg)?.incrementStatistics( "commandsDroppedRX", ); @@ -3456,7 +3585,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, @@ -3511,12 +3640,12 @@ export class Driver extends TypedEventEmitter // If we receive a CC from a node while the controller is not ready yet, // we can't do anything with it, but logging it may assume that it can access the controller. // To prevent this problem, we just ignore CCs until the controller is ready - if (!this._controller && isCommandClassContainer(msg)) return; + if (!this._controller && containsCC(msg)) return; // If the message could be decoded, forward it to the send thread if (msg) { let wasMessageLogged = false; - if (isCommandClassContainer(msg)) { + if (isCommandRequest(msg) && containsCC(msg)) { // SecurityCCCommandEncapsulationNonceGet is two commands in one, but // we're not set up to handle things like this. Reply to the nonce get // and handle the encapsulation part normally @@ -3524,7 +3653,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 @@ -3729,7 +3858,7 @@ export class Driver extends TypedEventEmitter } private mustReplyWithSecurityS2MOS( - msg: ApplicationCommandRequest | BridgeApplicationCommandRequest, + msg: ContainsCC & CommandRequest, ): boolean { // We're looking for a singlecast S2-encapsulated request if (msg.frameType !== "singlecast") return false; @@ -3740,7 +3869,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; @@ -3765,7 +3894,7 @@ export class Driver extends TypedEventEmitter if ( (e.code === ZWaveErrorCodes.Security2CC_NoSPAN || e.code === ZWaveErrorCodes.Security2CC_CannotDecode) - && isCommandClassContainer(msg) + && containsCC(msg) ) { // Decoding the command failed because no SPAN has been established with the other node const nodeId = msg.getNodeId()!; @@ -3791,7 +3920,7 @@ export class Driver extends TypedEventEmitter // Ensure that we're not flooding the queue with unnecessary NonceReports const isS2NonceReport = (t: Transaction) => t.message.getNodeId() === nodeId - && isCommandClassContainer(t.message) + && containsCC(t.message) && t.message.command instanceof Security2CCNonceReport; const message: string = @@ -3850,9 +3979,7 @@ export class Driver extends TypedEventEmitter direction: "outbound", }); // Send the node our nonce, and use the chance to re-sync the MPAN if necessary - const s2MulticastOutOfSync = - (msg instanceof ApplicationCommandRequest - || msg instanceof BridgeApplicationCommandRequest) + const s2MulticastOutOfSync = isCommandRequest(msg) && this.mustReplyWithSecurityS2MOS(msg); node.commandClasses["Security 2"] @@ -3873,7 +4000,7 @@ export class Driver extends TypedEventEmitter } else if ( (e.code === ZWaveErrorCodes.Security2CC_NoMPAN || e.code === ZWaveErrorCodes.Security2CC_CannotDecodeMulticast) - && isCommandClassContainer(msg) + && containsCC(msg) ) { // Decoding the command failed because the MPAN used by the other node // is not known to us yet @@ -3921,11 +4048,11 @@ export class Driver extends TypedEventEmitter */ public handleMissingNodeACK( transaction: Transaction & { - message: INodeQuery; + message: HasNodeId; }, 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) @@ -4090,7 +4217,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. @@ -4353,7 +4480,7 @@ export class Driver extends TypedEventEmitter * Assembles partial CCs of in a message body. Returns `true` when the message is complete and can be handled further. * If the message expects another partial one, this returns `false`. */ - private assemblePartialCCs(msg: Message & ICommandClassContainer): boolean { + private assemblePartialCCs(msg: CommandRequest & ContainsCC): boolean { let command: CommandClass | undefined = msg.command; // We search for the every CC that provides us with a session ID // There might be newly-completed CCs that contain a partial CC, @@ -4378,7 +4505,11 @@ 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, { + ...this.getCCParsingContext(), + sourceNodeId: msg.command.nodeId as number, + frameType: msg.frameType, + }); // Ensure there are no errors assertValidCCs(msg); } catch (e) { @@ -4467,7 +4598,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, @@ -4484,7 +4615,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, }); @@ -4532,7 +4663,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, }); @@ -4894,8 +5025,8 @@ ${handlers.length} left`, private async handleRequest(msg: Message): Promise { let handlers: RequestHandlerEntry[] | undefined; - if (isNodeQuery(msg) || isCommandClassContainer(msg)) { - const node = this.getNodeUnsafe(msg); + if (hasNodeId(msg) || containsCC(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) { @@ -4913,8 +5044,10 @@ ${handlers.length} left`, } } - // It could also be that this is the node's response for a CC that we sent, but where the ACK is delayed - if (isCommandClassContainer(msg)) { + if (isCommandRequest(msg) && containsCC(msg)) { + const nodeId = msg.getNodeId()!; + + // It could also be that this is the node's response for a CC that we sent, but where the ACK is delayed const currentMessage = this.queue.currentTransaction ?.getCurrentMessage(); if ( @@ -4933,20 +5066,10 @@ ${handlers.length} left`, currentMessage.prematureNodeUpdate = msg; return; } - } - if (isCommandClassContainer(msg)) { // For further actions, we are only interested in the innermost CC this.unwrapCommands(msg); - } - // Otherwise go through the static handlers - if ( - msg instanceof ApplicationCommandRequest - || msg instanceof BridgeApplicationCommandRequest - ) { - // we handle ApplicationCommandRequests differently because they are handled by the nodes directly - const nodeId = msg.command.nodeId; // cannot handle ApplicationCommandRequests without a controller if (this._controller == undefined) { this.driverLog.print( @@ -5180,17 +5303,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; @@ -5206,23 +5339,32 @@ ${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; } - public unwrapCommands(msg: Message & ICommandClassContainer): void { + public unwrapCommands(msg: Message & ContainsCC): void { // Unwrap encapsulating CCs until we get to the core while ( isEncapsulatingCommandClass(msg.command) @@ -5274,7 +5416,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 @@ -5323,9 +5465,9 @@ ${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)) { + if (isSendData(msg) || hasNodeId(msg)) { // This shouldn't happen, but just in case if (!node) return; @@ -5385,7 +5527,7 @@ ${handlers.length} left`, if (currentNormalMsg?.getNodeId() !== targetNode.id) { return false; } - if (!isCommandClassContainer(currentNormalMsg)) { + if (!containsCC(currentNormalMsg)) { return false; } @@ -5424,7 +5566,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; @@ -5699,8 +5841,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.getEncodingContext()), { sendData: (data) => this.writeSerial(data), sendDataAbort: () => this.abortSendData(), @@ -5838,8 +5986,8 @@ ${handlers.length} left`, this.ensureReady(); let node: ZWaveNode | undefined; - if (isNodeQuery(msg) || isCommandClassContainer(msg)) { - node = this.getNodeUnsafe(msg); + if (hasNodeId(msg) || containsCC(msg)) { + node = this.tryGetNode(msg); } if (options.priority == undefined) { @@ -5899,6 +6047,7 @@ ${handlers.length} left`, // Create the transaction const { generator, resultPromise } = createMessageGenerator( this, + this.getEncodingContext(), msg, (msg, _result) => { this.handleSerialAPICommandResult(msg, options, _result); @@ -5964,7 +6113,7 @@ ${handlers.length} left`, } else { // For other messages to the node, just check for successful completion. If the callback is not OK, // we might not be able to communicate with the node. Sending another message is not a good idea. - maybeSendToSleep = isNodeQuery(msg) + maybeSendToSleep = hasNodeId(msg) && result && isSuccessIndicator(result) && result.isOK(); @@ -6019,7 +6168,7 @@ ${handlers.length} left`, public createSendDataMessage( command: CommandClass, options: Omit = {}, - ): SendDataMessage { + ): SendDataMessage & ContainsCC { // Automatically encapsulate commands before sending if (options.autoEncapsulate !== false) { command = this.encapsulateCommands(command, options); @@ -6031,12 +6180,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, }); @@ -6048,12 +6198,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, }); @@ -6082,7 +6233,7 @@ ${handlers.length} left`, msg.nodeUpdateTimeout = options.reportTimeoutMs; } - return msg; + return msg as SendDataMessage & ContainsCC; } /** @@ -6092,7 +6243,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< @@ -6105,7 +6256,7 @@ ${handlers.length} left`, const resp = await this.sendMessage(msg, options); // And unwrap the response if there was any - if (isCommandClassContainer(resp)) { + if (containsCC(resp)) { this.unwrapCommands(resp); return resp.command as unknown as TResponse; } @@ -6141,9 +6292,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, ); @@ -6174,7 +6326,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, @@ -6183,8 +6335,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); } @@ -6252,8 +6411,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.getEncodingContext()), + ); this.driverLog.logMessage(abort, { direction: "outbound", }); @@ -6371,12 +6532,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), @@ -6410,8 +6571,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; @@ -6498,7 +6659,7 @@ ${handlers.length} left`, case messageIsPing(msg): case transaction.priority === MessagePriority.Immediate: // We also don't want to immediately send the node to sleep when it wakes up - case isCommandClassContainer(msg) + case containsCC(msg) && msg.command instanceof WakeUpCCNoMoreInformation: // compat queries because they will be recreated when the node wakes up case transaction.tag === "compat": @@ -6832,7 +6993,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)) { @@ -6840,7 +7003,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(() => { @@ -6874,16 +7039,21 @@ ${handlers.length} left`, /** Computes the maximum net CC payload size for the given CC or SendDataRequest */ public computeNetCCPayloadSize( - commandOrMsg: CommandClass | SendDataRequest | SendDataBridgeRequest, + commandOrMsg: + | CommandClass + | (SendDataRequest | SendDataBridgeRequest) & ContainsCC, ignoreEncapsulation: boolean = false, ): number { // Recreate the correct encapsulation structure - let msg: SendDataRequest | SendDataBridgeRequest; + let msg: (SendDataRequest | SendDataBridgeRequest) & ContainsCC; if (isSendDataSinglecast(commandOrMsg)) { msg = commandOrMsg; } else { const SendDataConstructor = this.getSendDataSinglecastConstructor(); - msg = new SendDataConstructor(this, { command: commandOrMsg }); + msg = new SendDataConstructor({ + sourceNodeId: this.ownNodeId, + command: commandOrMsg, + }) as (SendDataRequest | SendDataBridgeRequest) & ContainsCC; } if (!ignoreEncapsulation) { msg.command = this.encapsulateCommands( @@ -6937,12 +7107,13 @@ ${handlers.length} left`, } public exceedsMaxPayloadLength(msg: SendDataMessage): boolean { - return msg.serializeCC().length > this.getMaxPayloadLength(msg); + return msg.serializeCC(this.getEncodingContext()).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 @@ -7354,7 +7525,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..8acbd488e616 100644 --- a/packages/zwave-js/src/lib/driver/MessageGenerators.ts +++ b/packages/zwave-js/src/lib/driver/MessageGenerators.ts @@ -9,7 +9,6 @@ import { SupervisionCCReport, SupervisionCommand, getInnermostCommandClass, - isCommandClassContainer, } from "@zwave-js/cc"; import { SecurityCCCommandEncapsulation, @@ -42,25 +41,29 @@ import { ZWaveErrorCodes, mergeSupervisionResults, } from "@zwave-js/core"; +import { type CCEncodingContext } from "@zwave-js/host"; import type { Message } from "@zwave-js/serial"; +import { + type SendDataMessage, + isSendData, + isTransmitReport, +} from "@zwave-js/serial/serialapi"; +import { type ContainsCC, containsCC } from "@zwave-js/serial/serialapi"; import { getErrorMessage } from "@zwave-js/shared"; import { wait } from "alcalzone-shared/async"; import { type DeferredPromise, createDeferredPromise, } from "alcalzone-shared/deferred-promise"; -import { - isSendData, - isTransmitReport, -} from "../serialapi/transport/SendDataShared"; import type { Driver } from "./Driver"; import type { MessageGenerator } from "./Transaction"; -export type MessageGeneratorImplementation = ( +export type MessageGeneratorImplementation = ( /** A reference to the driver */ driver: Driver, + ctx: CCEncodingContext, /** The "primary" message */ - message: Message, + message: T, /** * A hook to get notified about each sent message and the result of the Serial API call * without waiting for the message generator to finish completely. @@ -73,7 +76,7 @@ export type MessageGeneratorImplementation = ( function maybePartialNodeUpdate(sent: Message, received: Message): boolean { // Some commands are returned in multiple segments, which may take longer than // the configured timeout. - if (!isCommandClassContainer(sent) || !isCommandClassContainer(received)) { + if (!containsCC(sent) || !containsCC(received)) { return false; } @@ -125,9 +128,10 @@ function getNodeUpdateTimeout( } /** A simple message generator that simply sends a message, waits for the ACK (and the response if one is expected) */ -export const simpleMessageGenerator: MessageGeneratorImplementation = +export const simpleMessageGenerator: MessageGeneratorImplementation = async function*( driver, + ctx, msg, onMessageSent, additionalCommandTimeoutMs = 0, @@ -206,265 +210,275 @@ 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) { - // Make sure we can send this message - if (!isSendData(msg)) { +export const maybeTransportServiceGenerator: MessageGeneratorImplementation< + SendDataMessage & ContainsCC +> = async function*( + driver, + ctx, + msg, + onMessageSent, + additionalCommandTimeoutMs, +) { + // Make sure we can send this message + /*if (!isSendData(msg) || !containsCC(msg)) { throw new ZWaveError( "Cannot use the Transport Service message generator for messages that are not SendData!", ZWaveErrorCodes.Argument_Invalid, ); - } else if (typeof msg.command.nodeId !== "number") { - throw new ZWaveError( - "Cannot use the Transport Service message generator for multicast commands!", - ZWaveErrorCodes.Argument_Invalid, - ); - } + } else*/ if (typeof msg.command.nodeId !== "number") { + throw new ZWaveError( + "Cannot use the Transport Service message generator for multicast commands!", + ZWaveErrorCodes.Argument_Invalid, + ); + } - const node = msg.getNodeUnsafe(driver); - const mayUseTransportService = - node?.supportsCC(CommandClasses["Transport Service"]) - && node.getCCVersion(CommandClasses["Transport Service"]) >= 2; + const node = msg.tryGetNode(driver); + const mayUseTransportService = + node?.supportsCC(CommandClasses["Transport Service"]) + && node.getCCVersion(CommandClasses["Transport Service"]) >= 2; - if (!mayUseTransportService || !driver.exceedsMaxPayloadLength(msg)) { - // Transport Service isn't needed for this message - return yield* simpleMessageGenerator( - driver, - msg, - onMessageSent, - additionalCommandTimeoutMs, - ); - } + if (!mayUseTransportService || !driver.exceedsMaxPayloadLength(msg)) { + // Transport Service isn't needed for this message + return yield* simpleMessageGenerator( + driver, + ctx, + msg, + onMessageSent, + additionalCommandTimeoutMs, + ); + } - // Send the command split into multiple segments - const payload = msg.serializeCC(); - const numSegments = Math.ceil(payload.length / MAX_SEGMENT_SIZE); - const segmentDelay = numSegments > RELAXED_TIMING_THRESHOLD - ? TransportServiceTimeouts.relaxedTimingDelayR2 - : 0; - const sessionId = driver.getNextTransportServiceSessionId(); - const nodeId = msg.command.nodeId; - - // Since the command is never logged, we do it here - driver.driverLog.print( - "The following message is too large, using Transport Service to transmit it:", + // Send the command split into multiple segments + const payload = msg.serializeCC(ctx); + const numSegments = Math.ceil(payload.length / MAX_SEGMENT_SIZE); + const segmentDelay = numSegments > RELAXED_TIMING_THRESHOLD + ? TransportServiceTimeouts.relaxedTimingDelayR2 + : 0; + const sessionId = driver.getNextTransportServiceSessionId(); + const nodeId = msg.command.nodeId; + + // Since the command is never logged, we do it here + driver.driverLog.print( + "The following message is too large, using Transport Service to transmit it:", + ); + driver.driverLog.logMessage(msg, { + direction: "outbound", + }); + + // I don't see an elegant way to wait for possible responses, so we just register a handler in the driver + // and remember the received commands + let unhandledResponses: TransportServiceCC[] = []; + const { unregister: unregisterHandler } = driver.registerCommandHandler( + (cc) => + cc.nodeId === nodeId + && (cc instanceof TransportServiceCCSegmentWait + || (cc instanceof TransportServiceCCSegmentRequest + && cc.sessionId === sessionId)), + (cc) => { + unhandledResponses.push(cc as TransportServiceCC); + }, + ); + + const receivedSegmentWait = () => { + const index = unhandledResponses.findIndex( + (cc) => cc instanceof TransportServiceCCSegmentWait, ); - driver.driverLog.logMessage(msg, { - direction: "outbound", - }); + if (index >= 0) { + const cc = unhandledResponses[ + index + ] as TransportServiceCCSegmentWait; + unhandledResponses.splice(index, 1); + return cc; + } + }; - // I don't see an elegant way to wait for possible responses, so we just register a handler in the driver - // and remember the received commands - let unhandledResponses: TransportServiceCC[] = []; - const { unregister: unregisterHandler } = driver.registerCommandHandler( - (cc) => - cc.nodeId === nodeId - && (cc instanceof TransportServiceCCSegmentWait - || (cc instanceof TransportServiceCCSegmentRequest - && cc.sessionId === sessionId)), - (cc) => { - unhandledResponses.push(cc as TransportServiceCC); - }, + const receivedSegmentRequest = () => { + const index = unhandledResponses.findIndex( + (cc) => cc instanceof TransportServiceCCSegmentRequest, ); + if (index >= 0) { + const cc = unhandledResponses[ + index + ] as TransportServiceCCSegmentRequest; + unhandledResponses.splice(index, 1); + return cc; + } + }; - const receivedSegmentWait = () => { - const index = unhandledResponses.findIndex( - (cc) => cc instanceof TransportServiceCCSegmentWait, - ); - if (index >= 0) { - const cc = unhandledResponses[ - index - ] as TransportServiceCCSegmentWait; - unhandledResponses.splice(index, 1); - return cc; - } - }; + // We have to deal with multiple messages, but can only return a single result. + // Therefore we use the last one as the result. + let result!: Message; - const receivedSegmentRequest = () => { - const index = unhandledResponses.findIndex( - (cc) => cc instanceof TransportServiceCCSegmentRequest, - ); - if (index >= 0) { - const cc = unhandledResponses[ - index - ] as TransportServiceCCSegmentRequest; - unhandledResponses.splice(index, 1); - return cc; - } - }; + try { + attempts: for (let attempt = 1; attempt <= 2; attempt++) { + driver.controllerLog.logNode(nodeId, { + message: + `Beginning Transport Service TX session #${sessionId}...`, + level: "debug", + direction: "outbound", + }); - // We have to deal with multiple messages, but can only return a single result. - // Therefore we use the last one as the result. - let result!: Message; + // Clear the list of unhandled responses + unhandledResponses = []; + // Fill the list of unsent segments + const unsentSegments = new Array(numSegments) + .fill(0) + .map((_, i) => i); + let didRetryLastSegment = false; + let isFirstTransferredSegment = true; + + while (unsentSegments.length > 0) { + // Wait if necessary + if (isFirstTransferredSegment) { + isFirstTransferredSegment = false; + } else if (segmentDelay) { + await wait(segmentDelay, true); + } + const segment = unsentSegments.shift()!; - try { - attempts: for (let attempt = 1; attempt <= 2; attempt++) { - driver.controllerLog.logNode(nodeId, { - message: - `Beginning Transport Service TX session #${sessionId}...`, - level: "debug", - direction: "outbound", + const chunk = payload.subarray( + segment * MAX_SEGMENT_SIZE, + (segment + 1) * MAX_SEGMENT_SIZE, + ); + let cc: TransportServiceCC; + if (segment === 0) { + cc = new TransportServiceCCFirstSegment({ + nodeId, + sessionId, + datagramSize: payload.length, + partialDatagram: chunk, + }); + } else { + cc = new TransportServiceCCSubsequentSegment({ + nodeId, + sessionId, + datagramSize: payload.length, + datagramOffset: segment * MAX_SEGMENT_SIZE, + partialDatagram: chunk, + }); + } + + const tmsg = driver.createSendDataMessage(cc, { + autoEncapsulate: false, + maxSendAttempts: msg.maxSendAttempts, + transmitOptions: msg.transmitOptions, }); + result = yield* simpleMessageGenerator( + driver, + ctx, + tmsg, + onMessageSent, + ); - // Clear the list of unhandled responses - unhandledResponses = []; - // Fill the list of unsent segments - const unsentSegments = new Array(numSegments) - .fill(0) - .map((_, i) => i); - let didRetryLastSegment = false; - let isFirstTransferredSegment = true; - - while (unsentSegments.length > 0) { - // Wait if necessary - if (isFirstTransferredSegment) { - isFirstTransferredSegment = false; - } else if (segmentDelay) { - await wait(segmentDelay, true); - } - const segment = unsentSegments.shift()!; + let segmentComplete: + | TransportServiceCCSegmentComplete + | undefined = undefined; + // After sending the last segment, wait for a SegmentComplete response, at the same time + // give the node a chance to send a SegmentWait or SegmentRequest(s) + if (segment === numSegments - 1) { + segmentComplete = await driver + .waitForCommand( + (cc) => + cc.nodeId === nodeId + && cc + instanceof TransportServiceCCSegmentComplete + && cc.sessionId === sessionId, + TransportServiceTimeouts.segmentCompleteR2, + ) + .catch(() => undefined); + } - const chunk = payload.subarray( - segment * MAX_SEGMENT_SIZE, - (segment + 1) * MAX_SEGMENT_SIZE, - ); - let cc: TransportServiceCC; - if (segment === 0) { - cc = new TransportServiceCCFirstSegment(driver, { - nodeId, - sessionId, - datagramSize: payload.length, - partialDatagram: chunk, - }); - } else { - cc = new TransportServiceCCSubsequentSegment(driver, { - nodeId, - sessionId, - datagramSize: payload.length, - datagramOffset: segment * MAX_SEGMENT_SIZE, - partialDatagram: chunk, - }); - } + if (segmentComplete) { + // We're done! + driver.controllerLog.logNode(nodeId, { + message: + `Transport Service TX session #${sessionId} complete`, + level: "debug", + direction: "outbound", + }); + break attempts; + } - const tmsg = driver.createSendDataMessage(cc, { - autoEncapsulate: false, - maxSendAttempts: msg.maxSendAttempts, - transmitOptions: msg.transmitOptions, + // If we received a SegmentWait, we need to wait and restart + const segmentWait = receivedSegmentWait(); + if (segmentWait) { + const waitTime = segmentWait.pendingSegments * 100; + driver.controllerLog.logNode(nodeId, { + message: + `Restarting Transport Service TX session #${sessionId} in ${waitTime} ms...`, + level: "debug", }); - result = yield* simpleMessageGenerator( - driver, - tmsg, - onMessageSent, - ); - let segmentComplete: - | TransportServiceCCSegmentComplete - | undefined = undefined; - // After sending the last segment, wait for a SegmentComplete response, at the same time - // give the node a chance to send a SegmentWait or SegmentRequest(s) - if (segment === numSegments - 1) { - segmentComplete = await driver - .waitForCommand( - (cc) => - cc.nodeId === nodeId - && cc - instanceof TransportServiceCCSegmentComplete - && cc.sessionId === sessionId, - TransportServiceTimeouts.segmentCompleteR2, - ) - .catch(() => undefined); - } + await wait(waitTime, true); + continue attempts; + } + + // If the node requested missing segments, add them to the list of unsent segments and continue transmitting + let segmentRequest: + | TransportServiceCCSegmentRequest + | undefined = undefined; + let readdedSegments = false; + while ((segmentRequest = receivedSegmentRequest())) { + unsentSegments.push( + segmentRequest.datagramOffset / MAX_SEGMENT_SIZE, + ); + readdedSegments = true; + } + if (readdedSegments) continue; - if (segmentComplete) { - // We're done! + // If we didn't receive anything after sending the last segment, retry the last segment + if (segment === numSegments - 1) { + if (didRetryLastSegment) { driver.controllerLog.logNode(nodeId, { message: - `Transport Service TX session #${sessionId} complete`, + `Transport Service TX session #${sessionId} failed`, level: "debug", direction: "outbound", }); break attempts; - } - - // If we received a SegmentWait, we need to wait and restart - const segmentWait = receivedSegmentWait(); - if (segmentWait) { - const waitTime = segmentWait.pendingSegments * 100; + } else { + // Try the last segment again driver.controllerLog.logNode(nodeId, { message: - `Restarting Transport Service TX session #${sessionId} in ${waitTime} ms...`, + `Transport Service TX session #${sessionId}: Segment Complete missing - re-transmitting last segment...`, level: "debug", + direction: "outbound", }); - - await wait(waitTime, true); - continue attempts; - } - - // If the node requested missing segments, add them to the list of unsent segments and continue transmitting - let segmentRequest: - | TransportServiceCCSegmentRequest - | undefined = undefined; - let readdedSegments = false; - while ((segmentRequest = receivedSegmentRequest())) { - unsentSegments.push( - segmentRequest.datagramOffset / MAX_SEGMENT_SIZE, - ); - readdedSegments = true; - } - if (readdedSegments) continue; - - // If we didn't receive anything after sending the last segment, retry the last segment - if (segment === numSegments - 1) { - if (didRetryLastSegment) { - driver.controllerLog.logNode(nodeId, { - message: - `Transport Service TX session #${sessionId} failed`, - level: "debug", - direction: "outbound", - }); - break attempts; - } else { - // Try the last segment again - driver.controllerLog.logNode(nodeId, { - message: - `Transport Service TX session #${sessionId}: Segment Complete missing - re-transmitting last segment...`, - level: "debug", - direction: "outbound", - }); - didRetryLastSegment = true; - unsentSegments.unshift(segment); - continue; - } + didRetryLastSegment = true; + unsentSegments.unshift(segment); + continue; } } } - } finally { - // We're done, unregister the handler - unregisterHandler(); } + } finally { + // We're done, unregister the handler + unregisterHandler(); + } - // Transport Service CCs do not expect a node update and have no knowledge about the encapsulated CC. - // Therefore we need to replicate the waiting from simpleMessageGenerator here + // Transport Service CCs do not expect a node update and have no knowledge about the encapsulated CC. + // Therefore we need to replicate the waiting from simpleMessageGenerator here - // If the sent message expects an update from the node, wait for it - if (msg.expectsNodeUpdate()) { - // TODO: Figure out if we can handle premature updates with Transport Service CC - const timeout = getNodeUpdateTimeout( - driver, - msg, - additionalCommandTimeoutMs, - ); - return waitForNodeUpdate(driver, msg, timeout); - } + // If the sent message expects an update from the node, wait for it + if (msg.expectsNodeUpdate()) { + // TODO: Figure out if we can handle premature updates with Transport Service CC + const timeout = getNodeUpdateTimeout( + driver, + msg, + additionalCommandTimeoutMs, + ); + return waitForNodeUpdate(driver, msg, timeout); + } - return result; - }; + return result; +}; /** A simple (internal) generator that simply sends a command, and optionally returns the response command */ async function* sendCommandGenerator< TResponse extends CommandClass = CommandClass, >( driver: Driver, + ctx: CCEncodingContext, command: CommandClass, onMessageSent: (msg: Message, result: Message | undefined) => void, options?: SendCommandOptions, @@ -473,357 +487,373 @@ async function* sendCommandGenerator< const resp = yield* maybeTransportServiceGenerator( driver, + ctx, msg, onMessageSent, ); - if (resp && isCommandClassContainer(resp)) { + if (resp && containsCC(resp)) { driver.unwrapCommands(resp); return resp.command as TResponse; } } /** A message generator for security encapsulated messages (S0) */ -export const secureMessageGeneratorS0: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { - if (!isSendData(msg)) { +export const secureMessageGeneratorS0: MessageGeneratorImplementation< + SendDataMessage & ContainsCC +> = 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!", ZWaveErrorCodes.Argument_Invalid, ); - } else if (typeof msg.command.nodeId !== "number") { - throw new ZWaveError( - "Cannot use the S0 message generator for multicast commands!", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (!(msg.command instanceof SecurityCCCommandEncapsulation)) { + } else*/ if (typeof msg.command.nodeId !== "number") { + throw new ZWaveError( + "Cannot use the S0 message generator for multicast commands!", + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (!(msg.command instanceof SecurityCCCommandEncapsulation)) { + throw new ZWaveError( + "The S0 message generator can only be used for Security S0 command encapsulation!", + ZWaveErrorCodes.Argument_Invalid, + ); + } + + // Step 1: Acquire a nonce + const secMan = driver.securityManager!; + const nodeId = msg.command.nodeId; + let additionalTimeoutMs: number | undefined; + + // Try to get a free nonce before requesting a new one + let nonce: Buffer | undefined = secMan.getFreeNonce(nodeId); + if (!nonce) { + // No free nonce, request a new one + const cc = new SecurityCCNonceGet({ + nodeId: nodeId, + endpointIndex: msg.command.endpointIndex, + }); + const nonceResp = yield* sendCommandGenerator< + SecurityCCNonceReport + >( + driver, + ctx, + cc, + (msg, result) => { + additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); + onMessageSent(msg, result); + }, + { + // Only try getting a nonce once + maxSendAttempts: 1, + }, + ); + if (!nonceResp) { throw new ZWaveError( - "The S0 message generator can only be used for Security S0 command encapsulation!", - ZWaveErrorCodes.Argument_Invalid, + "No nonce received from the node, cannot send secure command!", + ZWaveErrorCodes.SecurityCC_NoNonce, ); } + nonce = nonceResp.nonce; + } + msg.command.nonce = nonce; - // Step 1: Acquire a nonce - const secMan = driver.securityManager!; - const nodeId = msg.command.nodeId; - let additionalTimeoutMs: number | undefined; - - // Try to get a free nonce before requesting a new one - let nonce: Buffer | undefined = secMan.getFreeNonce(nodeId); - if (!nonce) { - // No free nonce, request a new one - const cc = new SecurityCCNonceGet(driver, { - nodeId: nodeId, - endpoint: msg.command.endpointIndex, - }); - const nonceResp = yield* sendCommandGenerator< - SecurityCCNonceReport - >( - driver, - cc, - (msg, result) => { - additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); - onMessageSent(msg, result); - }, - { - // Only try getting a nonce once - maxSendAttempts: 1, - }, - ); - if (!nonceResp) { - throw new ZWaveError( - "No nonce received from the node, cannot send secure command!", - ZWaveErrorCodes.SecurityCC_NoNonce, - ); - } - nonce = nonceResp.nonce; - } - msg.command.nonce = nonce; + // Now send the actual secure command + return yield* simpleMessageGenerator( + driver, + ctx, + msg, + onMessageSent, + additionalTimeoutMs, + ); +}; - // Now send the actual secure command - return yield* simpleMessageGenerator( - driver, - msg, - onMessageSent, - additionalTimeoutMs, +/** A message generator for security encapsulated messages (S2) */ +export const secureMessageGeneratorS2: MessageGeneratorImplementation< + SendDataMessage & ContainsCC +> = async function*(driver, ctx, msg, onMessageSent) { + if (!isSendData(msg) || !containsCC(msg)) { + throw new ZWaveError( + "Cannot use the S2 message generator for a command that's not a SendData message!", + ZWaveErrorCodes.Argument_Invalid, ); - }; + } else if (typeof msg.command.nodeId !== "number") { + throw new ZWaveError( + "Cannot use the S2 message generator for multicast commands!", + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (!(msg.command instanceof Security2CCMessageEncapsulation)) { + throw new ZWaveError( + "The S2 message generator can only be used for Security S2 command encapsulation!", + ZWaveErrorCodes.Argument_Invalid, + ); + } -/** A message generator for security encapsulated messages (S2) */ -export const secureMessageGeneratorS2: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { - if (!isSendData(msg)) { - throw new ZWaveError( - "Cannot use the S2 message generator for a command that's not a SendData message!", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (typeof msg.command.nodeId !== "number") { - throw new ZWaveError( - "Cannot use the S2 message generator for multicast commands!", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (!(msg.command instanceof Security2CCMessageEncapsulation)) { + const nodeId = msg.command.nodeId; + const secMan = driver.getSecurityManager2(nodeId)!; + const spanState = secMan.getSPANState(nodeId); + let additionalTimeoutMs: number | undefined; + + // We need a new nonce when there is no shared SPAN state, or the SPAN state is for a lower security class + // than the command we want to send + const expectedSecurityClass = msg.command.securityClass + ?? driver.getHighestSecurityClass(nodeId); + + if ( + spanState.type === SPANState.None + || spanState.type === SPANState.LocalEI + || (spanState.type === SPANState.SPAN + && spanState.securityClass !== SecurityClass.Temporary + && spanState.securityClass !== expectedSecurityClass) + ) { + // Request a new nonce + + // No free nonce, request a new one + const cc = new Security2CCNonceGet({ + nodeId: nodeId, + endpointIndex: msg.command.endpointIndex, + }); + const nonceResp = yield* sendCommandGenerator< + Security2CCNonceReport + >( + driver, + ctx, + cc, + (msg, result) => { + additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); + onMessageSent(msg, result); + }, + { + // Only try getting a nonce once + maxSendAttempts: 1, + }, + ); + if (!nonceResp) { throw new ZWaveError( - "The S2 message generator can only be used for Security S2 command encapsulation!", - ZWaveErrorCodes.Argument_Invalid, + "No nonce received from the node, cannot send secure command!", + ZWaveErrorCodes.Security2CC_NoSPAN, ); } - const nodeId = msg.command.nodeId; - const secMan = driver.getSecurityManager2(nodeId)!; - const spanState = secMan.getSPANState(nodeId); - let additionalTimeoutMs: number | undefined; + // Storing the nonce is not necessary, this will be done automatically when the nonce is received + } - // We need a new nonce when there is no shared SPAN state, or the SPAN state is for a lower security class - // than the command we want to send - const expectedSecurityClass = msg.command.securityClass - ?? driver.getHighestSecurityClass(nodeId); + // Now send the actual secure command + let response = yield* maybeTransportServiceGenerator( + driver, + ctx, + msg, + onMessageSent, + additionalTimeoutMs, + ); - if ( - spanState.type === SPANState.None - || spanState.type === SPANState.LocalEI - || (spanState.type === SPANState.SPAN - && spanState.securityClass !== SecurityClass.Temporary - && spanState.securityClass !== expectedSecurityClass) - ) { - // Request a new nonce + // If we want to make sure that a node understood a SET-type S2-encapsulated message, we either need to use + // Supervision and wait for the Supervision Report (handled by the simpleMessageGenerator), or we need to add a + // short delay between commands and wait if a NonceReport is received. + // However, in situations where timing is critical (e.g. S2 bootstrapping), verifyDelivery is set to false, and we don't do this. + let nonceReport: Security2CCNonceReport | undefined; + if ( + isTransmitReport(response) + && msg.command.verifyDelivery + && !msg.command.expectsCCResponse() + && !msg.command.getEncapsulatedCC( + CommandClasses.Supervision, + SupervisionCommand.Get, + ) + ) { + nonceReport = await driver + .waitForCommand( + (cc) => + cc.nodeId === nodeId + && cc instanceof Security2CCNonceReport, + 500, + ) + .catch(() => undefined); + } else if ( + containsCC(response) + && response.command instanceof Security2CCNonceReport + ) { + nonceReport = response.command; + } - // No free nonce, request a new one - const cc = new Security2CCNonceGet(driver, { - nodeId: nodeId, - endpoint: msg.command.endpointIndex, - }); - const nonceResp = yield* sendCommandGenerator< - Security2CCNonceReport - >( - driver, - cc, - (msg, result) => { - additionalTimeoutMs = Math.ceil(msg.rtt! / 1e6); - onMessageSent(msg, result); - }, - { - // Only try getting a nonce once - maxSendAttempts: 1, - }, - ); - if (!nonceResp) { - throw new ZWaveError( - "No nonce received from the node, cannot send secure command!", - ZWaveErrorCodes.Security2CC_NoSPAN, - ); + if (nonceReport) { + if (nonceReport.SOS && nonceReport.receiverEI) { + // The node couldn't decrypt the last command we sent it. Invalidate + // the shared SPAN, since it did the same + secMan.storeRemoteEI(nodeId, nonceReport.receiverEI); + } + if (nonceReport.MOS) { + const multicastGroupId = msg.command.getMulticastGroupId(); + if (multicastGroupId != undefined) { + // The node couldn't decrypt the previous S2 multicast. Tell it the MPAN (again) + const mpan = secMan.getInnerMPANState(multicastGroupId); + if (mpan) { + // Replace the MGRP extension with an MPAN extension + msg.command.extensions = msg.command.extensions.filter( + (e) => !(e instanceof MGRPExtension), + ); + msg.command.extensions.push( + new MPANExtension({ + groupId: multicastGroupId, + innerMPANState: mpan, + }), + ); + } } - - // Storing the nonce is not necessary, this will be done automatically when the nonce is received } + driver.controllerLog.logNode(nodeId, { + message: + `failed to decode the message, retrying with SPAN extension...`, + direction: "none", + }); - // Now send the actual secure command - let response = yield* maybeTransportServiceGenerator( + // Send the message again + msg.prepareRetransmission(); + response = yield* maybeTransportServiceGenerator( driver, + ctx, msg, onMessageSent, additionalTimeoutMs, ); - // If we want to make sure that a node understood a SET-type S2-encapsulated message, we either need to use - // Supervision and wait for the Supervision Report (handled by the simpleMessageGenerator), or we need to add a - // short delay between commands and wait if a NonceReport is received. - // However, in situations where timing is critical (e.g. S2 bootstrapping), verifyDelivery is set to false, and we don't do this. - let nonceReport: Security2CCNonceReport | undefined; if ( - isTransmitReport(response) - && msg.command.verifyDelivery - && !msg.command.expectsCCResponse() - && !msg.command.getEncapsulatedCC( - CommandClasses.Supervision, - SupervisionCommand.Get, - ) - ) { - nonceReport = await driver - .waitForCommand( - (cc) => - cc.nodeId === nodeId - && cc instanceof Security2CCNonceReport, - 500, - ) - .catch(() => undefined); - } else if ( - isCommandClassContainer(response) + containsCC(response) && response.command instanceof Security2CCNonceReport ) { - nonceReport = response.command; - } - - if (nonceReport) { - if (nonceReport.SOS && nonceReport.receiverEI) { - // The node couldn't decrypt the last command we sent it. Invalidate - // the shared SPAN, since it did the same - secMan.storeRemoteEI(nodeId, nonceReport.receiverEI); - } - if (nonceReport.MOS) { - const multicastGroupId = msg.command.getMulticastGroupId(); - if (multicastGroupId != undefined) { - // The node couldn't decrypt the previous S2 multicast. Tell it the MPAN (again) - const mpan = secMan.getInnerMPANState(multicastGroupId); - if (mpan) { - // Replace the MGRP extension with an MPAN extension - msg.command.extensions = msg.command.extensions.filter( - (e) => !(e instanceof MGRPExtension), - ); - msg.command.extensions.push( - new MPANExtension({ - groupId: multicastGroupId, - innerMPANState: mpan, - }), - ); - } - } - } + // No dice driver.controllerLog.logNode(nodeId, { message: - `failed to decode the message, retrying with SPAN extension...`, + `failed to decode the message after re-transmission with SPAN extension, dropping the message.`, direction: "none", + level: "warn", }); - - // Send the message again - msg.prepareRetransmission(); - response = yield* maybeTransportServiceGenerator( - driver, - msg, - onMessageSent, - additionalTimeoutMs, + throw new ZWaveError( + "The node failed to decode the message.", + ZWaveErrorCodes.Security2CC_CannotDecode, ); - - if ( - isCommandClassContainer(response) - && response.command instanceof Security2CCNonceReport - ) { - // No dice - driver.controllerLog.logNode(nodeId, { - message: - `failed to decode the message after re-transmission with SPAN extension, dropping the message.`, - direction: "none", - level: "warn", - }); - throw new ZWaveError( - "The node failed to decode the message.", - ZWaveErrorCodes.Security2CC_CannotDecode, - ); - } } + } - return response; - }; + return response; +}; /** A message generator for security encapsulated messages (S2 Multicast) */ -export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = - async function*(driver, msg, onMessageSent) { - if (!isSendData(msg)) { - throw new ZWaveError( - "Cannot use the S2 multicast message generator for a command that's not a SendData message!", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (msg.command.isSinglecast()) { - throw new ZWaveError( - "Cannot use the S2 multicast message generator for singlecast commands!", - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (!(msg.command instanceof Security2CCMessageEncapsulation)) { - throw new ZWaveError( - "The S2 multicast message generator can only be used for Security S2 command encapsulation!", - ZWaveErrorCodes.Argument_Invalid, - ); - } +export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation< + SendDataMessage & ContainsCC +> = async function*(driver, ctx, msg, onMessageSent) { + if (!isSendData(msg) || !containsCC(msg)) { + throw new ZWaveError( + "Cannot use the S2 multicast message generator for a command that's not a SendData message!", + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (msg.command.isSinglecast()) { + throw new ZWaveError( + "Cannot use the S2 multicast message generator for singlecast commands!", + ZWaveErrorCodes.Argument_Invalid, + ); + } else if (!(msg.command instanceof Security2CCMessageEncapsulation)) { + throw new ZWaveError( + "The S2 multicast message generator can only be used for Security S2 command encapsulation!", + ZWaveErrorCodes.Argument_Invalid, + ); + } - const groupId = msg.command.getMulticastGroupId(); - if (groupId == undefined) { - throw new ZWaveError( - "Cannot use the S2 multicast message generator without a multicast group ID!", - ZWaveErrorCodes.Argument_Invalid, - ); - } + const groupId = msg.command.getMulticastGroupId(); + if (groupId == undefined) { + throw new ZWaveError( + "Cannot use the S2 multicast message generator without a multicast group ID!", + ZWaveErrorCodes.Argument_Invalid, + ); + } - const secMan = driver.getSecurityManager2(msg.command.nodeId)!; - const group = secMan.getMulticastGroup(groupId); - if (!group) { - throw new ZWaveError( - `Multicast group ${groupId} does not exist!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } + const secMan = driver.getSecurityManager2(msg.command.nodeId)!; + const group = secMan.getMulticastGroup(groupId); + if (!group) { + throw new ZWaveError( + `Multicast group ${groupId} does not exist!`, + ZWaveErrorCodes.Argument_Invalid, + ); + } - // Send the multicast command. We remember the transmit report and treat it as the result of the multicast command - const response = yield* simpleMessageGenerator( - driver, - msg, - onMessageSent, + // 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, + ); + + // If a node in the group is out of sync, we need to transfer the MPAN state we're going to use for the next command. + // Therefore increment the MPAN state now and not after the followups like the specs mention + secMan.tryIncrementMPAN(groupId); + + // Unwrap the command again, so we can make the following encapsulation depend on the target node + driver.unwrapCommands(msg); + const command = msg.command; + // Remember the original encapsulation flags + const encapsulationFlags = command.encapsulationFlags; + + // In case someone sneaked a node ID into the group multiple times, remove duplicates for the singlecast followups + // Otherwise, the node will increase its MPAN multiple times, going out of sync. + const distinctNodeIDs = [...new Set(group.nodeIDs)]; + + const supervisionResults: (SupervisionResult | undefined)[] = []; + + // Now do singlecast followups with every node in the group + for (const nodeId of distinctNodeIDs) { + // Point the CC at the target node + (command.nodeId as number) = nodeId; + // Figure out if supervision should be used + command.encapsulationFlags = encapsulationFlags; + command.toggleEncapsulationFlag( + EncapsulationFlags.Supervision, + SupervisionCC.mayUseSupervision(driver, command), ); - // If a node in the group is out of sync, we need to transfer the MPAN state we're going to use for the next command. - // Therefore increment the MPAN state now and not after the followups like the specs mention - secMan.tryIncrementMPAN(groupId); - - // Unwrap the command again, so we can make the following encapsulation depend on the target node - driver.unwrapCommands(msg); - const command = msg.command; - // Remember the original encapsulation flags - const encapsulationFlags = command.encapsulationFlags; - - // In case someone sneaked a node ID into the group multiple times, remove duplicates for the singlecast followups - // Otherwise, the node will increase its MPAN multiple times, going out of sync. - const distinctNodeIDs = [...new Set(group.nodeIDs)]; - - const supervisionResults: (SupervisionResult | undefined)[] = []; - - // Now do singlecast followups with every node in the group - for (const nodeId of distinctNodeIDs) { - // Point the CC at the target node - (command.nodeId as number) = nodeId; - // Figure out if supervision should be used - command.encapsulationFlags = encapsulationFlags; - command.toggleEncapsulationFlag( - EncapsulationFlags.Supervision, - SupervisionCC.mayUseSupervision(driver, command), - ); + const scMsg = driver.createSendDataMessage(command, { + transmitOptions: msg.transmitOptions, + maxSendAttempts: msg.maxSendAttempts, + }); + // The outermost command is a Security2CCMessageEncapsulation, we need to set the MGRP extension on this again + (scMsg.command as Security2CCMessageEncapsulation).extensions.push( + new MGRPExtension({ groupId }), + ); - const scMsg = driver.createSendDataMessage(command, { - transmitOptions: msg.transmitOptions, - maxSendAttempts: msg.maxSendAttempts, - }); - // The outermost command is a Security2CCMessageEncapsulation, we need to set the MGRP extension on this again - (scMsg.command as Security2CCMessageEncapsulation).extensions.push( - new MGRPExtension({ groupId }), + // Reuse the S2 singlecast message generator for sending this new message + try { + const scResponse = yield* secureMessageGeneratorS2( + driver, + ctx, + scMsg, + onMessageSent, ); + if ( + containsCC(scResponse) + && scResponse.command + instanceof Security2CCMessageEncapsulation + && scResponse.command.hasMOSExtension() + ) { + // The node understood the S2 singlecast followup, but told us that its MPAN is out of sync + + const innerMPANState = secMan.getInnerMPANState(groupId); + // This should always be defined, but better not throw unnecessarily here + if (innerMPANState) { + const cc = new Security2CCMessageEncapsulation({ + nodeId, + extensions: [ + new MPANExtension({ + groupId, + innerMPANState, + }), + ], + }); - // Reuse the S2 singlecast message generator for sending this new message - try { - const scResponse = yield* secureMessageGeneratorS2( - driver, - scMsg, - onMessageSent, - ); - if ( - isCommandClassContainer(scResponse) - && scResponse.command - instanceof Security2CCMessageEncapsulation - && scResponse.command.hasMOSExtension() - ) { - // The node understood the S2 singlecast followup, but told us that its MPAN is out of sync - - const innerMPANState = secMan.getInnerMPANState(groupId); - // This should always be defined, but better not throw unnecessarily here - if (innerMPANState) { - const cc = new Security2CCMessageEncapsulation(driver, { - nodeId, - extensions: [ - new MPANExtension({ - groupId, - innerMPANState, - }), - ], - }); - - // Send it the MPAN - yield* sendCommandGenerator(driver, cc, onMessageSent, { + // Send it the MPAN + yield* sendCommandGenerator( + driver, + ctx, + cc, + onMessageSent, + { // Seems we need these options or some nodes won't accept the nonce transmitOptions: TransmitOptions.ACK | TransmitOptions.AutoRoute, @@ -833,57 +863,59 @@ export const secureMessageGeneratorS2Multicast: MessageGeneratorImplementation = priority: MessagePriority.Immediate, // We don't want failures causing us to treat the node as asleep or dead changeNodeStatusOnMissingACK: false, - }); - } - } - - // Collect supervision results if possible - if (isCommandClassContainer(scResponse)) { - const supervisionReport = scResponse.command - .getEncapsulatedCC( - CommandClasses.Supervision, - SupervisionCommand.Report, - ) as SupervisionCCReport | undefined; - - supervisionResults.push( - supervisionReport?.toSupervisionResult(), + }, ); } - } catch (e) { - driver.driverLog.print(getErrorMessage(e), "error"); - // TODO: Figure out how we got here, and what to do now. - // In any case, keep going with the next nodes - // Report that there was a failure, so the application can show it - supervisionResults.push({ - status: SupervisionStatus.Fail, - }); } - } - const finalSupervisionResult = mergeSupervisionResults( - supervisionResults, - ); - 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, { - nodeId: NODE_ID_BROADCAST, - sessionId: 0, // fake - moreUpdatesFollow: false, // fake - ...(finalSupervisionResult as any), + // Collect supervision results if possible + if (containsCC(scResponse)) { + const supervisionReport = scResponse.command + .getEncapsulatedCC( + CommandClasses.Supervision, + SupervisionCommand.Report, + ) as SupervisionCCReport | undefined; + + supervisionResults.push( + supervisionReport?.toSupervisionResult(), + ); + } + } catch (e) { + driver.driverLog.print(getErrorMessage(e), "error"); + // TODO: Figure out how we got here, and what to do now. + // In any case, keep going with the next nodes + // Report that there was a failure, so the application can show it + supervisionResults.push({ + status: SupervisionStatus.Fail, }); - const ret = new (driver.getSendDataSinglecastConstructor())( - driver, - { command: cc }, - ); - return ret; - } else { - return response; } - }; + } + + const finalSupervisionResult = mergeSupervisionResults( + supervisionResults, + ); + 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({ + nodeId: NODE_ID_BROADCAST, + sessionId: 0, // fake + moreUpdatesFollow: false, // fake + ...(finalSupervisionResult as any), + }); + const ret = new (driver.getSendDataSinglecastConstructor())({ + sourceNodeId: driver.ownNodeId, + command: cc, + }); + return ret; + } else { + return response; + } +}; export function createMessageGenerator( driver: Driver, + ctx: CCEncodingContext, msg: Message, onMessageSent: (msg: Message, result: Message | undefined) => void, ): { @@ -903,27 +935,41 @@ export function createMessageGenerator( start: () => { async function* gen() { // Determine which message generator implementation should be used - let implementation: MessageGeneratorImplementation = + let implementation: MessageGeneratorImplementation = simpleMessageGenerator; if (isSendData(msg)) { + if (!containsCC(msg)) { + throw new ZWaveError( + "Cannot create a message generator for a message that doesn't contain a command class", + ZWaveErrorCodes.Argument_Invalid, + ); + } if ( msg.command instanceof Security2CCMessageEncapsulation ) { - implementation = msg.command.isSinglecast() - ? secureMessageGeneratorS2 - : secureMessageGeneratorS2Multicast; + implementation = ( + msg.command.isSinglecast() + ? secureMessageGeneratorS2 + : secureMessageGeneratorS2Multicast + ) as MessageGeneratorImplementation; } else if ( msg.command instanceof SecurityCCCommandEncapsulation ) { - implementation = secureMessageGeneratorS0; + implementation = + secureMessageGeneratorS0 as MessageGeneratorImplementation< + Message + >; } else if (msg.command.isSinglecast()) { - implementation = maybeTransportServiceGenerator; + implementation = + maybeTransportServiceGenerator as MessageGeneratorImplementation< + Message + >; } } // 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..a527f22c612e 100644 --- a/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts +++ b/packages/zwave-js/src/lib/driver/SerialAPICommandMachine.ts @@ -4,6 +4,7 @@ import { isMultiStageCallback, isSuccessIndicator, } from "@zwave-js/serial"; +import { isSendData } from "@zwave-js/serial/serialapi"; import { type InterpreterFrom, type MachineConfig, @@ -13,7 +14,6 @@ import { createMachine, raise, } from "xstate"; -import { isSendData } from "../serialapi/transport/SendDataShared"; import type { ZWaveOptions } from "./ZWaveOptions"; export interface SerialAPICommandStateSchema { @@ -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/StateMachineShared.ts b/packages/zwave-js/src/lib/driver/StateMachineShared.ts index cd75b7510fbc..e44b5544964a 100644 --- a/packages/zwave-js/src/lib/driver/StateMachineShared.ts +++ b/packages/zwave-js/src/lib/driver/StateMachineShared.ts @@ -6,29 +6,29 @@ import { isZWaveError, } from "@zwave-js/core"; import type { Message } from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; -import { - type AnyStateMachine, - Interpreter, - type InterpreterFrom, - type InterpreterOptions, -} from "xstate"; import { SendDataBridgeRequest, type SendDataBridgeRequestTransmitReport, SendDataMulticastBridgeRequest, type SendDataMulticastBridgeRequestTransmitReport, -} from "../serialapi/transport/SendDataBridgeMessages"; +} from "@zwave-js/serial/serialapi"; import { SendDataMulticastRequest, type SendDataMulticastRequestTransmitReport, SendDataRequest, type SendDataRequestTransmitReport, -} from "../serialapi/transport/SendDataMessages"; +} from "@zwave-js/serial/serialapi"; import { isSendData, isSendDataTransmitReport, -} from "../serialapi/transport/SendDataShared"; +} from "@zwave-js/serial/serialapi"; +import { getEnumMemberName } from "@zwave-js/shared"; +import { + type AnyStateMachine, + Interpreter, + type InterpreterFrom, + type InterpreterOptions, +} from "xstate"; import type { SerialAPICommandDoneData } from "./SerialAPICommandMachine"; import type { Transaction } from "./Transaction"; diff --git a/packages/zwave-js/src/lib/driver/Transaction.test.ts b/packages/zwave-js/src/lib/driver/Transaction.test.ts index ef0d707d25d7..abfe6ccaa000 100644 --- a/packages/zwave-js/src/lib/driver/Transaction.test.ts +++ b/packages/zwave-js/src/lib/driver/Transaction.test.ts @@ -1,12 +1,12 @@ import { NoOperationCC } from "@zwave-js/cc/NoOperationCC"; import { MessagePriority } from "@zwave-js/core"; import { type Message, getDefaultPriority } from "@zwave-js/serial"; +import { GetControllerVersionRequest } from "@zwave-js/serial/serialapi"; +import { RemoveFailedNodeRequest } from "@zwave-js/serial/serialapi"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; import test from "ava"; import type { ZWaveNode } from "../node/Node"; import { NodeStatus } from "../node/_Types"; -import { GetControllerVersionRequest } from "../serialapi/capability/GetControllerVersionMessages"; -import { RemoveFailedNodeRequest } from "../serialapi/network-mgmt/RemoveFailedNodeMessages"; -import { SendDataRequest } from "../serialapi/transport/SendDataMessages"; import type { Driver } from "./Driver"; import { type MessageGenerator, @@ -23,6 +23,9 @@ function createDummyMessageGenerator(msg: Message): MessageGenerator { self: undefined, current: undefined, parent: undefined as any, + reset() { + this.current = undefined; + }, }; } @@ -49,8 +52,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 +150,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 +167,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 +263,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 +276,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 +359,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 +383,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 +404,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..1d2a75f697e3 100644 --- a/packages/zwave-js/src/lib/log/Driver.test.ts +++ b/packages/zwave-js/src/lib/log/Driver.test.ts @@ -35,12 +35,6 @@ test.before(async (t) => { loadConfiguration: false, skipNodeInterview: true, skipControllerIdentification: true, - // beforeStartup(mockPort) { - // controller = new MockController({ serial: mockPort }); - // controller.defineBehavior( - // ...createDefaultMockControllerBehaviors(), - // ); - // }, }); t.context.driver = driver; @@ -88,7 +82,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..3d4133413b90 100644 --- a/packages/zwave-js/src/lib/log/Driver.ts +++ b/packages/zwave-js/src/lib/log/Driver.ts @@ -1,6 +1,5 @@ import { type CommandClass, - isCommandClassContainer, isEncapsulatingCommandClass, isMultiEncapsulatingCommandClass, } from "@zwave-js/cc"; @@ -16,6 +15,7 @@ import { } from "@zwave-js/core"; import type { Message, ResponseRole } from "@zwave-js/serial"; import { FunctionType, MessageType } from "@zwave-js/serial"; +import { containsCC } from "@zwave-js/serial/serialapi"; import { getEnumMemberName } from "@zwave-js/shared"; import type { Driver } from "../driver/Driver"; import { type TransactionQueue } from "../driver/Queue"; @@ -122,7 +122,7 @@ export class DriverLogger extends ZWaveLoggerBase { return; } - const isCCContainer = isCommandClassContainer(message); + const isCCContainer = containsCC(message); const logEntry = message.toLogEntry(); let msg: string[] = [tagify(logEntry.tags)]; @@ -136,7 +136,7 @@ export class DriverLogger extends ZWaveLoggerBase { try { // If possible, include information about the CCs - if (isCommandClassContainer(message)) { + if (isCCContainer) { // Remove the default payload message and draw a bracket msg = msg.filter((line) => !line.startsWith("│ payload:")); @@ -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]"; @@ -209,7 +209,7 @@ export class DriverLogger extends ZWaveLoggerBase { ) }]` : ""; - const command = isCommandClassContainer(trns.message) + const command = containsCC(trns.message) ? `: ${trns.message.command.constructor.name}` : ""; message += `\n· ${prefix} ${ 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..ad6875a565b2 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,9 +85,9 @@ 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, + endpointIndex: "index" in endpoint ? endpoint.index : undefined, requestedCC: receivedCC.requestedCC, ccVersion: version, }); @@ -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..3ee9b3d4f241 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,11 +36,9 @@ import { type ValueIDProperties, ZWavePlusNodeType, ZWavePlusRoleType, - defaultCCValueOptions, entryControlEventTypeLabels, - getCCValues, + getEffectiveCCVersion, getImplementedVersion, - isCommandClassContainer, utils as ccUtils, } from "@zwave-js/cc"; import { @@ -83,11 +73,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 +83,6 @@ import { ManufacturerSpecificCCGet, ManufacturerSpecificCCValues, } from "@zwave-js/cc/ManufacturerSpecificCC"; -import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; import { MultilevelSwitchCC, MultilevelSwitchCCSet, @@ -155,18 +141,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 +153,10 @@ import { type NotificationState, ProtocolVersion, Protocols, + type QuerySecurityClasses, type RSSI, RssiError, SecurityClass, - type SecurityClassOwner, type SendCommandOptions, type SetValueOptions, type SinglecastCC, @@ -185,12 +164,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 +180,6 @@ import { getDSTInfo, getNotification, getNotificationValue, - isLongRangeNodeId, isRssiError, isSupervisionResult, isTransmissionError, @@ -218,15 +194,26 @@ 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 { + type ApplicationUpdateRequest, + ApplicationUpdateRequestNodeInfoReceived, + ApplicationUpdateRequestNodeInfoRequestFailed, +} from "@zwave-js/serial/serialapi"; +import { + GetNodeProtocolInfoRequest, + type GetNodeProtocolInfoResponse, +} from "@zwave-js/serial/serialapi"; +import { + RequestNodeInfoRequest, + RequestNodeInfoResponse, +} from "@zwave-js/serial/serialapi"; +import { containsCC } from "@zwave-js/serial/serialapi"; import { Mixin, - ObjectKeyMap, type TypedEventEmitter, cloneDeep, discreteLinearSearch, @@ -236,9 +223,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,55 +232,29 @@ 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, - ApplicationUpdateRequestNodeInfoReceived, - ApplicationUpdateRequestNodeInfoRequestFailed, -} from "../serialapi/application/ApplicationUpdateRequest"; -import { - GetNodeProtocolInfoRequest, - type GetNodeProtocolInfoResponse, -} from "../serialapi/network-mgmt/GetNodeProtocolInfoMessages"; -import { - RequestNodeInfoRequest, - RequestNodeInfoResponse, -} 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); + this.cancelAllScheduledPolls(); } - 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; - } - } - - /** @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,268 +857,27 @@ 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); - - // 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; - } - - /** - * @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; + 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( @@ -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; @@ -1914,11 +1133,11 @@ export class ZWaveNode extends Endpoint // The GetNodeProtocolInfoRequest needs to know the node ID to distinguish // between ZWLR and ZW classic. We store it on the driver's context, so it // can be retrieved when needed. - this.driver.requestContext.set(FunctionType.GetNodeProtocolInfo, { + this.driver.requestStorage.set(FunctionType.GetNodeProtocolInfo, { 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, )!; @@ -3360,7 +2578,7 @@ protocol version: ${this.protocolVersion}`; // Ensure that we're not flooding the queue with unnecessary NonceReports (GH#1059) const isNonceReport = (t: Transaction) => t.message.getNodeId() === this.id - && isCommandClassContainer(t.message) + && containsCC(t.message) && t.message.command instanceof SecurityCCNonceReport; if (this.driver.hasPendingTransactions(isNonceReport)) { @@ -3437,7 +2655,7 @@ protocol version: ${this.protocolVersion}`; // Ensure that we're not flooding the queue with unnecessary NonceReports (GH#1059) const isNonceReport = (t: Transaction) => t.message.getNodeId() === this.id - && isCommandClassContainer(t.message) + && containsCC(t.message) && t.message.command instanceof Security2CCNonceReport; if (this.driver.hasPendingTransactions(isNonceReport)) { @@ -4934,8 +4152,18 @@ protocol version: ${this.protocolVersion}`; } private handleDeviceResetLocallyNotification( - _cmd: DeviceResetLocallyCCNotification, + cmd: DeviceResetLocallyCCNotification, ): void { + if (cmd.endpointIndex !== 0) { + // The notification MUST be issued by the root device, otherwise it is likely a corrupted message + this.driver.controllerLog.logNode(this.id, { + message: + `Received reset locally notification from non-root endpoint - ignoring it...`, + direction: "inbound", + }); + return; + } + // Handling this command can take a few seconds and require communication with the node. // If it was received with Supervision, we need to acknowledge it immediately. Therefore // defer the handling half a second. @@ -5113,6 +4341,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 +4495,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 +4548,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); } @@ -5466,1076 +4696,161 @@ protocol version: ${this.protocolVersion}`; } } - 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; - } + private async handleTimeGet(command: TimeCCTimeGet): Promise { + const endpoint = this.getEndpoint(command.endpointIndex) ?? this; - // 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", - }); + 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 +4911,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 +6374,6 @@ ${formatRouteHealthCheckSummary(this.id, otherNode.id, summary)}`, : undefined; const ccInstance = CommandClass.createInstanceUnchecked( - this.driver, this, valueId.commandClass, ); @@ -8167,4 +6437,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/NodeReadyMachine.test.ts b/packages/zwave-js/src/lib/node/NodeReadyMachine.test.ts index ec082a9dbfd3..6bc0ee19faa7 100644 --- a/packages/zwave-js/src/lib/node/NodeReadyMachine.test.ts +++ b/packages/zwave-js/src/lib/node/NodeReadyMachine.test.ts @@ -30,8 +30,6 @@ test("when the driver is restarted from cache, the node should be ready as soon const service = startMachine(t, testMachine); service.send("RESTART_FROM_CACHE"); t.is(service.getSnapshot().value, "readyIfNotDead"); - // service.send("MAYBE_DEAD"); - // t.is(service.getSnapshot().value, "readyIfNotDead"); service.send("NOT_DEAD"); t.is(service.getSnapshot().value, "ready"); }); 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..6993355b6910 --- /dev/null +++ b/packages/zwave-js/src/lib/node/mixins/50_Endpoints.ts @@ -0,0 +1,182 @@ +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[] { + 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..6b09c2ab80d2 --- /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, +} from "@zwave-js/cc"; +import { + CRC16_CCITT, + CommandClasses, + EncapsulationFlags, + type Firmware, + SecurityClass, + ZWaveError, + ZWaveErrorCodes, + securityClassIsS2, + timespan, +} from "@zwave-js/core"; +import { containsCC } from "@zwave-js/serial/serialapi"; +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 + && containsCC(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..63c1c85fc34a 100644 --- a/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/MultiChannel.ts @@ -44,10 +44,10 @@ const encapsulateMultiChannelCC: MockNodeBehavior = { (multiChannelEncap as MultiChannelCCCommandEncapsulation) .destination as number; - response.cc = new MultiChannelCCCommandEncapsulation(self.host, { + response.cc = new MultiChannelCCCommandEncapsulation({ nodeId: response.cc.nodeId, + endpointIndex: source, encapsulated: response.cc, - endpoint: source, destination, }); } @@ -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/_Types.ts b/packages/zwave-js/src/lib/serialapi/_Types.ts deleted file mode 100644 index 4d2f19f4d5b6..000000000000 --- a/packages/zwave-js/src/lib/serialapi/_Types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ZWaveLibraryTypes } from "@zwave-js/core/safe"; -export type { ZWaveApiVersion } from "@zwave-js/core/safe"; diff --git a/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts b/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts deleted file mode 100644 index fb2517781f87..000000000000 --- a/packages/zwave-js/src/lib/serialapi/application/ApplicationCommandRequest.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { CommandClass, type ICommandClassContainer } from "@zwave-js/cc"; -import { - type FrameType, - type MessageOrCCLogEntry, - MessagePriority, - type MessageRecord, - type SinglecastCC, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, - parseNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; - -export enum ApplicationCommandStatusFlags { - RoutedBusy = 0b1, // A response route is locked by the application - LowPower = 0b10, // Received at low output power level - - TypeSingle = 0b0000, // Received a single cast frame - TypeBroad = 0b0100, // Received a broad cast frame - TypeMulti = 0b1000, // Received a multi cast frame - TypeMask = TypeSingle | TypeBroad | TypeMulti, - - Explore = 0b10000, // Received an explore frame - - ForeignFrame = 0b0100_0000, // Received a foreign frame (only promiscuous mode) - ForeignHomeId = 0b1000_0000, // The received frame is received from a foreign HomeID. Only Controllers in Smart Start AddNode mode can receive this status. -} - -interface ApplicationCommandRequestOptions extends MessageBaseOptions { - command: CommandClass; - frameType?: ApplicationCommandRequest["frameType"]; - routedBusy?: boolean; -} - -@messageTypes(MessageType.Request, FunctionType.ApplicationCommand) -// This does not expect a response. The controller sends us this when a node sends a command -@priority(MessagePriority.Normal) -export class ApplicationCommandRequest extends Message - implements ICommandClassContainer -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ApplicationCommandRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - // first byte is a status flag - const status = this.payload[0]; - this.routedBusy = !!( - status & ApplicationCommandStatusFlags.RoutedBusy - ); - switch (status & ApplicationCommandStatusFlags.TypeMask) { - case ApplicationCommandStatusFlags.TypeMulti: - this.frameType = "multicast"; - break; - case ApplicationCommandStatusFlags.TypeBroad: - this.frameType = "broadcast"; - break; - default: - this.frameType = "singlecast"; - } - this.isExploreFrame = this.frameType === "broadcast" - && !!(status & ApplicationCommandStatusFlags.Explore); - this.isForeignFrame = !!( - status & ApplicationCommandStatusFlags.ForeignFrame - ); - this.fromForeignHomeId = !!( - status & ApplicationCommandStatusFlags.ForeignHomeId - ); - - // followed by a node ID - let offset = 1; - const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, - offset, - ); - offset += nodeIdBytes; - // and a command class - const commandLength = this.payload[offset++]; - this.command = CommandClass.from(this.host, { - data: this.payload.subarray(offset, offset + commandLength), - nodeId, - origin: options.origin, - frameType: this.frameType, - }) as SinglecastCC; - } else { - // TODO: This logic is unsound - if (!options.command.isSinglecast()) { - throw new ZWaveError( - `ApplicationCommandRequest can only be used for singlecast CCs`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.frameType = options.frameType ?? "singlecast"; - this.routedBusy = !!options.routedBusy; - this.command = options.command; - this.isExploreFrame = false; - this.isForeignFrame = false; - this.fromForeignHomeId = false; - } - } - - public readonly routedBusy: boolean; - public readonly frameType: FrameType; - public readonly isExploreFrame: boolean; - public readonly isForeignFrame: boolean; - public readonly fromForeignHomeId: boolean; - - // This needs to be writable or unwrapping MultiChannelCCs crashes - public command: SinglecastCC; // TODO: why is this a SinglecastCC? - - public override getNodeId(): number | undefined { - if (this.command.isSinglecast()) { - return this.command.nodeId; - } - return super.getNodeId(); - } - - public serialize(): Buffer { - const statusByte = (this.frameType === "broadcast" - ? ApplicationCommandStatusFlags.TypeBroad - : this.frameType === "multicast" - ? ApplicationCommandStatusFlags.TypeMulti - : 0) - | (this.routedBusy ? ApplicationCommandStatusFlags.RoutedBusy : 0); - - const serializedCC = this.command.serialize(); - const nodeId = encodeNodeID( - this.getNodeId() ?? this.host.ownNodeId, - this.host.nodeIdType, - ); - this.payload = Buffer.concat([ - Buffer.from([statusByte]), - nodeId, - Buffer.from([serializedCC.length]), - serializedCC, - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - const message: MessageRecord = {}; - if (this.frameType !== "singlecast") { - message.type = this.frameType; - } - return { - ...super.toLogEntry(), - message, - }; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts deleted file mode 100644 index bea2a275ec75..000000000000 --- a/packages/zwave-js/src/lib/serialapi/capability/GetControllerVersionMessages.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { cpp2js } from "@zwave-js/shared"; -import type { ZWaveLibraryTypes } from "../_Types"; - -@messageTypes(MessageType.Request, FunctionType.GetControllerVersion) -@expectedResponse(FunctionType.GetControllerVersion) -@priority(MessagePriority.Controller) -export class GetControllerVersionRequest extends Message {} - -export interface GetControllerVersionResponseOptions - extends MessageBaseOptions -{ - controllerType: ZWaveLibraryTypes; - libraryVersion: string; -} - -@messageTypes(MessageType.Response, FunctionType.GetControllerVersion) -export class GetControllerVersionResponse extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetControllerVersionResponseOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // The payload consists of a zero-terminated string and a uint8 for the controller type - this.libraryVersion = cpp2js(this.payload.toString("ascii")); - this.controllerType = this.payload[this.libraryVersion.length + 1]; - } else { - this.controllerType = options.controllerType; - this.libraryVersion = options.libraryVersion; - } - } - - public controllerType: ZWaveLibraryTypes; - public libraryVersion: string; - - public serialize(): Buffer { - this.payload = Buffer.concat([ - Buffer.from(`${this.libraryVersion}\0`, "ascii"), - Buffer.from([this.controllerType]), - ]); - - return super.serialize(); - } -} diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts deleted file mode 100644 index dbbe9b26d0c1..000000000000 --- a/packages/zwave-js/src/lib/serialapi/capability/GetLongRangeNodesMessages.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { - MessagePriority, - NUM_LR_NODEMASK_SEGMENT_BYTES, - NUM_LR_NODES_PER_SEGMENT, - encodeLongRangeNodeBitMask, - parseLongRangeNodeBitMask, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; - -export interface GetLongRangeNodesRequestOptions extends MessageBaseOptions { - segmentNumber: number; -} - -@messageTypes(MessageType.Request, FunctionType.GetLongRangeNodes) -@expectedResponse(FunctionType.GetLongRangeNodes) -@priority(MessagePriority.Controller) -export class GetLongRangeNodesRequest extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetLongRangeNodesRequestOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.segmentNumber = this.payload[0]; - } else { - this.segmentNumber = options.segmentNumber; - } - } - - public segmentNumber: number; - - public serialize(): Buffer { - this.payload = Buffer.from([this.segmentNumber]); - return super.serialize(); - } -} - -export interface GetLongRangeNodesResponseOptions extends MessageBaseOptions { - moreNodes: boolean; - segmentNumber: number; - nodeIds: number[]; -} - -@messageTypes(MessageType.Response, FunctionType.GetLongRangeNodes) -export class GetLongRangeNodesResponse extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetLongRangeNodesResponseOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.moreNodes = this.payload[0] != 0; - this.segmentNumber = this.payload[1]; - const listLength = this.payload[2]; - - const listStart = 3; - const listEnd = listStart + listLength; - if (listEnd <= this.payload.length) { - const nodeBitMask = this.payload.subarray( - listStart, - listEnd, - ); - this.nodeIds = parseLongRangeNodeBitMask( - nodeBitMask, - this.listStartNode(), - ); - } else { - this.nodeIds = []; - } - } else { - this.moreNodes = options.moreNodes; - this.segmentNumber = options.segmentNumber; - this.nodeIds = options.nodeIds; - } - } - - public moreNodes: boolean; - public segmentNumber: number; - public nodeIds: readonly number[]; - - public serialize(): Buffer { - this.payload = Buffer.allocUnsafe( - 3 + NUM_LR_NODEMASK_SEGMENT_BYTES, - ); - - this.payload[0] = this.moreNodes ? 1 : 0; - this.payload[1] = this.segmentNumber; - this.payload[2] = NUM_LR_NODEMASK_SEGMENT_BYTES; - - const nodeBitMask = encodeLongRangeNodeBitMask( - this.nodeIds, - this.listStartNode(), - ); - nodeBitMask.copy(this.payload, 3); - - return super.serialize(); - } - - private listStartNode(): number { - return 256 + NUM_LR_NODES_PER_SEGMENT * this.segmentNumber; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts deleted file mode 100644 index 8bd2d83b8630..000000000000 --- a/packages/zwave-js/src/lib/serialapi/capability/GetProtocolVersionMessages.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ProtocolType } from "@zwave-js/core"; -import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - messageTypes, - priority, -} from "@zwave-js/serial"; - -@messageTypes(MessageType.Request, FunctionType.GetProtocolVersion) -@priority(MessagePriority.Controller) -@expectedResponse(FunctionType.GetProtocolVersion) -export class GetProtocolVersionRequest extends Message {} - -@messageTypes(MessageType.Response, FunctionType.GetProtocolVersion) -export class GetProtocolVersionResponse extends Message { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.protocolType = this.payload[0]; - this.protocolVersion = [ - this.payload[1], - this.payload[2], - this.payload[3], - ].join("."); - if (this.payload.length >= 6) { - const appBuild = this.payload.readUInt16BE(4); - if (appBuild !== 0) this.applicationFrameworkBuildNumber = appBuild; - } - if (this.payload.length >= 22) { - const commitHash = this.payload.subarray(6, 22); - if (!commitHash.every((b) => b === 0)) { - this.gitCommitHash = commitHash.toString("hex"); - } - } - } - - public readonly protocolType: ProtocolType; - public readonly protocolVersion: string; - public readonly applicationFrameworkBuildNumber?: number; - public readonly gitCommitHash?: string; -} diff --git a/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts b/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts deleted file mode 100644 index 48c74811cb3a..000000000000 --- a/packages/zwave-js/src/lib/serialapi/capability/HardResetRequest.ts +++ /dev/null @@ -1,72 +0,0 @@ -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 MessageOptions, - MessageOrigin, - MessageType, - expectedCallback, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; - -@messageTypes(MessageType.Request, FunctionType.HardReset) -@priority(MessagePriority.Controller) -export class HardResetRequestBase extends Message { - public constructor(host: ZWaveHost, options?: MessageOptions) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== HardResetRequest - ) { - return new HardResetRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) !== HardResetCallback - ) { - return new HardResetCallback(host, options); - } - } - super(host, options); - } -} - -@expectedCallback(FunctionType.HardReset) -export class HardResetRequest extends HardResetRequestBase { - public serialize(): Buffer { - this.payload = Buffer.from([this.callbackId]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - }, - }; - } -} - -export class HardResetCallback extends HardResetRequestBase { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.callbackId = this.payload[0]; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - }, - }; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts b/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts deleted file mode 100644 index d8f5b9c1aea1..000000000000 --- a/packages/zwave-js/src/lib/serialapi/capability/LongRangeChannelMessages.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - ZWaveError, - 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, - MessageType, - type SuccessIndicator, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { getEnumMemberName } from "@zwave-js/shared"; - -@messageTypes(MessageType.Request, FunctionType.GetLongRangeChannel) -@expectedResponse(FunctionType.GetLongRangeChannel) -@priority(MessagePriority.Controller) -export class GetLongRangeChannelRequest extends Message {} - -@messageTypes(MessageType.Response, FunctionType.GetLongRangeChannel) -@priority(MessagePriority.Controller) -export class GetLongRangeChannelResponse extends Message { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.channel = this.payload[0]; - - if (this.payload.length >= 2) { - this.supportsAutoChannelSelection = - !!(this.payload[1] & 0b0001_0000); - this.autoChannelSelectionActive = !!(this.payload[1] & 0b0010_0000); - } else { - this.supportsAutoChannelSelection = false; - this.autoChannelSelectionActive = false; - } - } - - public readonly channel: - | LongRangeChannel.A - | LongRangeChannel.B - | LongRangeChannel.Unsupported; - public readonly supportsAutoChannelSelection: boolean; - public readonly autoChannelSelectionActive: boolean; -} - -export interface SetLongRangeChannelRequestOptions extends MessageBaseOptions { - channel: LongRangeChannel; -} - -@messageTypes(MessageType.Request, FunctionType.SetLongRangeChannel) -@priority(MessagePriority.Controller) -@expectedResponse(FunctionType.SetLongRangeChannel) -export class SetLongRangeChannelRequest extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SetLongRangeChannelRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.channel = options.channel; - } - } - - public channel: LongRangeChannel; - - public serialize(): Buffer { - this.payload = Buffer.from([this.channel]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - channel: getEnumMemberName(LongRangeChannel, this.channel), - }, - }; - } -} - -@messageTypes(MessageType.Response, FunctionType.SetLongRangeChannel) -export class SetLongRangeChannelResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.success = this.payload[0] !== 0; - } - - isOK(): boolean { - return this.success; - } - - public readonly success: boolean; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { success: this.success }, - }; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts b/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts deleted file mode 100644 index a84da19a4f33..000000000000 --- a/packages/zwave-js/src/lib/serialapi/misc/SetSerialApiTimeoutsMessages.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { MessagePriority } from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - messageTypes, - priority, -} from "@zwave-js/serial"; - -interface SetSerialApiTimeoutsRequestOptions extends MessageBaseOptions { - ackTimeout: number; - byteTimeout: number; -} - -@messageTypes(MessageType.Request, FunctionType.SetSerialApiTimeouts) -@expectedResponse(FunctionType.SetSerialApiTimeouts) -@priority(MessagePriority.Controller) -export class SetSerialApiTimeoutsRequest extends Message { - public constructor( - host: ZWaveHost, - options: SetSerialApiTimeoutsRequestOptions, - ) { - super(host, options); - this.ackTimeout = options.ackTimeout; - this.byteTimeout = options.byteTimeout; - } - - public ackTimeout: number; - public byteTimeout: number; - - public serialize(): Buffer { - this.payload = Buffer.from([ - Math.round(this.ackTimeout / 10), - Math.round(this.byteTimeout / 10), - ]); - return super.serialize(); - } -} - -@messageTypes(MessageType.Response, FunctionType.SetSerialApiTimeouts) -export class SetSerialApiTimeoutsResponse extends Message { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this._oldAckTimeout = this.payload[0] * 10; - this._oldByteTimeout = this.payload[1] * 10; - } - - private _oldAckTimeout: number; - public get oldAckTimeout(): number { - return this._oldAckTimeout; - } - - private _oldByteTimeout: number; - public get oldByteTimeout(): number { - return this._oldByteTimeout; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts deleted file mode 100644 index 7deec2f1ca05..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignReturnRouteMessages.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitStatus, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageType, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -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) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== AssignReturnRouteRequestTransmitReport - ) { - return new AssignReturnRouteRequestTransmitReport(host, options); - } - super(host, options); - } -} - -export interface AssignReturnRouteRequestOptions extends MessageBaseOptions { - nodeId: number; - destinationNodeId: number; -} - -@expectedResponse(FunctionType.AssignReturnRoute) -@expectedCallback(FunctionType.AssignReturnRoute) -export class AssignReturnRouteRequest extends AssignReturnRouteRequestBase - implements INodeQuery -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AssignReturnRouteRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - if (options.nodeId === options.destinationNodeId) { - throw new ZWaveError( - `The source and destination node must not be identical`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.nodeId = options.nodeId; - this.destinationNodeId = options.destinationNodeId; - } - } - - public nodeId: number; - public destinationNodeId: number; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); - const destinationNodeId = encodeNodeID( - this.destinationNodeId, - this.host.nodeIdType, - ); - - this.payload = Buffer.concat([ - nodeId, - destinationNodeId, - Buffer.from([this.callbackId]), - ]); - - return super.serialize(); - } -} - -@messageTypes(MessageType.Response, FunctionType.AssignReturnRoute) -export class AssignReturnRouteResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.hasStarted = this.payload[0] !== 0; - } - - public isOK(): boolean { - return this.hasStarted; - } - - public readonly hasStarted: boolean; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "has started": this.hasStarted }, - }; - } -} - -export class AssignReturnRouteRequestTransmitReport - extends AssignReturnRouteRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - } - - public isOK(): boolean { - // The other statuses are technically "not OK", but they are caused by - // not being able to contact the node. We don't want the node to be marked - // as dead because of that - return this.transmitStatus !== TransmitStatus.NoAck; - } - - public readonly transmitStatus: TransmitStatus; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "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 deleted file mode 100644 index 1eb2f7fc64e5..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitStatus, - encodeNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - type INodeQuery, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageOrigin, - MessageType, - type SuccessIndicator, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -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) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== AssignSUCReturnRouteRequest - ) { - return new AssignSUCReturnRouteRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) - !== AssignSUCReturnRouteRequestTransmitReport - ) { - return new AssignSUCReturnRouteRequestTransmitReport( - host, - options, - ); - } - } - - super(host, options); - } -} - -export interface AssignSUCReturnRouteRequestOptions extends MessageBaseOptions { - nodeId: number; -} - -function testAssignSUCReturnRouteCallback( - sent: AssignSUCReturnRouteRequest, - 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) - ) { - return true; - } - return callback.functionType === FunctionType.AssignSUCReturnRoute; -} - -@expectedResponse(FunctionType.AssignSUCReturnRoute) -@expectedCallback(testAssignSUCReturnRouteCallback) -export class AssignSUCReturnRouteRequest extends AssignSUCReturnRouteRequestBase - implements INodeQuery -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AssignSUCReturnRouteRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.nodeId = this.payload[0]; - this.callbackId = this.payload[1]; - } else { - this.nodeId = options.nodeId; - } - } - - public nodeId: number; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - - return super.serialize(); - } -} - -interface AssignSUCReturnRouteResponseOptions extends MessageBaseOptions { - wasExecuted: boolean; -} - -@messageTypes(MessageType.Response, FunctionType.AssignSUCReturnRoute) -export class AssignSUCReturnRouteResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AssignSUCReturnRouteResponseOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.wasExecuted = this.payload[0] !== 0; - } else { - this.wasExecuted = options.wasExecuted; - } - } - - public isOK(): boolean { - return this.wasExecuted; - } - - public wasExecuted: boolean; - - public serialize(): Buffer { - this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was executed": this.wasExecuted }, - }; - } -} - -interface AssignSUCReturnRouteRequestTransmitReportOptions - extends MessageBaseOptions -{ - transmitStatus: TransmitStatus; - callbackId: number; -} - -export class AssignSUCReturnRouteRequestTransmitReport - extends AssignSUCReturnRouteRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | AssignSUCReturnRouteRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - } else { - this.callbackId = options.callbackId; - this.transmitStatus = options.transmitStatus; - } - } - - public isOK(): boolean { - // The other statuses are technically "not OK", but they are caused by - // not being able to contact the node. We don't want the node to be marked - // as dead because of that - return this.transmitStatus !== TransmitStatus.NoAck; - } - - public transmitStatus: TransmitStatus; - - public serialize(): Buffer { - this.payload = Buffer.from([this.callbackId, this.transmitStatus]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "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 deleted file mode 100644 index 55d3e1dcd114..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteReturnRouteMessages.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitStatus, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageType, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -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) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== DeleteReturnRouteRequestTransmitReport - ) { - return new DeleteReturnRouteRequestTransmitReport(host, options); - } - super(host, options); - } -} - -export interface DeleteReturnRouteRequestOptions extends MessageBaseOptions { - nodeId: number; -} - -@expectedResponse(FunctionType.DeleteReturnRoute) -@expectedCallback(FunctionType.DeleteReturnRoute) -export class DeleteReturnRouteRequest extends DeleteReturnRouteRequestBase - implements INodeQuery -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | DeleteReturnRouteRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.nodeId = options.nodeId; - } - } - - public nodeId: number; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - - return super.serialize(); - } -} - -@messageTypes(MessageType.Response, FunctionType.DeleteReturnRoute) -export class DeleteReturnRouteResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.hasStarted = this.payload[0] !== 0; - } - - public isOK(): boolean { - return this.hasStarted; - } - - public readonly hasStarted: boolean; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "has started": this.hasStarted }, - }; - } -} - -export class DeleteReturnRouteRequestTransmitReport - extends DeleteReturnRouteRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - } - - public isOK(): boolean { - // The other statuses are technically "not OK", but they are caused by - // not being able to contact the node. We don't want the node to be marked - // as dead because of that - return this.transmitStatus !== TransmitStatus.NoAck; - } - - public readonly transmitStatus: TransmitStatus; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "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 deleted file mode 100644 index 40960535f621..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitStatus, - encodeNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import type { INodeQuery, SuccessIndicator } from "@zwave-js/serial"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageOrigin, - MessageType, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -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) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== DeleteSUCReturnRouteRequest - ) { - return new DeleteSUCReturnRouteRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) - !== DeleteSUCReturnRouteRequestTransmitReport - ) { - return new DeleteSUCReturnRouteRequestTransmitReport( - host, - options, - ); - } - } - - super(host, options); - } -} - -export interface DeleteSUCReturnRouteRequestOptions extends MessageBaseOptions { - nodeId: number; -} - -function testDeleteSUCReturnRouteCallback( - sent: DeleteSUCReturnRouteRequest, - 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) - ) { - return true; - } - return callback.functionType === FunctionType.DeleteSUCReturnRoute; -} - -@expectedResponse(FunctionType.DeleteSUCReturnRoute) -@expectedCallback(testDeleteSUCReturnRouteCallback) -export class DeleteSUCReturnRouteRequest extends DeleteSUCReturnRouteRequestBase - implements INodeQuery -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | DeleteSUCReturnRouteRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.nodeId = this.payload[0]; - this.callbackId = this.payload[1]; - } else { - this.nodeId = options.nodeId; - } - } - - public nodeId: number; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.nodeId, this.host.nodeIdType); - this.payload = Buffer.concat([nodeId, Buffer.from([this.callbackId])]); - - return super.serialize(); - } -} - -interface DeleteSUCReturnRouteResponseOptions extends MessageBaseOptions { - wasExecuted: boolean; -} - -@messageTypes(MessageType.Response, FunctionType.DeleteSUCReturnRoute) -export class DeleteSUCReturnRouteResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | DeleteSUCReturnRouteResponseOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.wasExecuted = this.payload[0] !== 0; - } else { - this.wasExecuted = options.wasExecuted; - } - } - - public isOK(): boolean { - return this.wasExecuted; - } - - public readonly wasExecuted: boolean; - - public serialize(): Buffer { - this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was executed": this.wasExecuted }, - }; - } -} - -interface DeleteSUCReturnRouteRequestTransmitReportOptions - extends MessageBaseOptions -{ - transmitStatus: TransmitStatus; - callbackId: number; -} - -export class DeleteSUCReturnRouteRequestTransmitReport - extends DeleteSUCReturnRouteRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | DeleteSUCReturnRouteRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - } else { - this.callbackId = options.callbackId; - this.transmitStatus = options.transmitStatus; - } - } - - public isOK(): boolean { - // The other statuses are technically "not OK", but they are caused by - // not being able to contact the node. We don't want the node to be marked - // as dead because of that - return this.transmitStatus !== TransmitStatus.NoAck; - } - - public readonly transmitStatus: TransmitStatus; - - public serialize(): Buffer { - this.payload = Buffer.from([this.callbackId, this.transmitStatus]); - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "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 deleted file mode 100644 index a13ae9071fef..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/GetNodeProtocolInfoMessages.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - type BasicDeviceClass, - type DataRate, - type FLiRS, - MessagePriority, - type NodeProtocolInfoAndDeviceClass, - type NodeType, - type ProtocolVersion, - encodeNodeID, - encodeNodeProtocolInfo, - isLongRangeNodeId, - parseNodeID, - parseNodeProtocolInfo, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { isObject } from "alcalzone-shared/typeguards"; - -interface GetNodeProtocolInfoRequestOptions extends MessageBaseOptions { - requestedNodeId: number; -} - -@messageTypes(MessageType.Request, FunctionType.GetNodeProtocolInfo) -@expectedResponse(FunctionType.GetNodeProtocolInfo) -@priority(MessagePriority.Controller) -export class GetNodeProtocolInfoRequest extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetNodeProtocolInfoRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.requestedNodeId = - parseNodeID(this.payload, this.host.nodeIdType, 0).nodeId; - } else { - this.requestedNodeId = options.requestedNodeId; - } - } - - // This must not be called nodeId or the message will be treated as a node query - // 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(); - } -} - -interface GetNodeProtocolInfoResponseOptions - extends MessageBaseOptions, NodeProtocolInfoAndDeviceClass -{} - -@messageTypes(MessageType.Response, FunctionType.GetNodeProtocolInfo) -export class GetNodeProtocolInfoResponse extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | GetNodeProtocolInfoResponseOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - // The context should contain the node ID the protocol info was requested for. - // We use it here to determine whether the node is long range. - let isLongRange = false; - if ( - isObject(options.context) - && "nodeId" in options.context - && typeof options.context.nodeId === "number" - ) { - isLongRange = isLongRangeNodeId(options.context.nodeId); - } - - const { hasSpecificDeviceClass, ...rest } = parseNodeProtocolInfo( - this.payload, - 0, - isLongRange, - ); - this.isListening = rest.isListening; - this.isFrequentListening = rest.isFrequentListening; - this.isRouting = rest.isRouting; - this.supportedDataRates = rest.supportedDataRates; - this.protocolVersion = rest.protocolVersion; - this.optionalFunctionality = rest.optionalFunctionality; - this.nodeType = rest.nodeType; - this.supportsSecurity = rest.supportsSecurity; - this.supportsBeaming = rest.supportsBeaming; - - // parse the device class - this.basicDeviceClass = this.payload[3]; - this.genericDeviceClass = this.payload[4]; - this.specificDeviceClass = hasSpecificDeviceClass - ? this.payload[5] - : 0x00; - } else { - this.isListening = options.isListening; - this.isFrequentListening = options.isFrequentListening; - this.isRouting = options.isRouting; - this.supportedDataRates = options.supportedDataRates; - this.protocolVersion = options.protocolVersion; - this.optionalFunctionality = options.optionalFunctionality; - this.nodeType = options.nodeType; - this.supportsSecurity = options.supportsSecurity; - this.supportsBeaming = options.supportsBeaming; - this.basicDeviceClass = options.basicDeviceClass; - this.genericDeviceClass = options.genericDeviceClass; - this.specificDeviceClass = options.specificDeviceClass; - } - } - - /** Whether this node is always listening or not */ - public isListening: boolean; - /** Indicates the wakeup interval if this node is a FLiRS node. `false` if it isn't. */ - public isFrequentListening: FLiRS; - /** Whether the node supports routing/forwarding messages. */ - public isRouting: boolean; - public supportedDataRates: DataRate[]; - public protocolVersion: ProtocolVersion; - /** Whether this node supports additional CCs besides the mandatory minimum */ - public optionalFunctionality: boolean; - /** Whether this node is a controller (can calculate routes) or an end node (relies on route info) */ - public nodeType: NodeType; - /** Whether this node supports security (S0 or S2) */ - public supportsSecurity: boolean; - /** Whether this node can issue wakeup beams to FLiRS nodes */ - public supportsBeaming: boolean; - - public basicDeviceClass: BasicDeviceClass; - public genericDeviceClass: number; - public specificDeviceClass: number; - - public serialize(): Buffer { - const protocolInfo = encodeNodeProtocolInfo({ - isListening: this.isListening, - isFrequentListening: this.isFrequentListening, - isRouting: this.isRouting, - supportedDataRates: this.supportedDataRates, - protocolVersion: this.protocolVersion, - optionalFunctionality: this.optionalFunctionality, - nodeType: this.nodeType, - supportsSecurity: this.supportsSecurity, - supportsBeaming: this.supportsBeaming, - hasSpecificDeviceClass: this.specificDeviceClass !== 0, - }); - this.payload = Buffer.concat([ - protocolInfo, - Buffer.from([ - this.basicDeviceClass, - this.genericDeviceClass, - this.specificDeviceClass, - ]), - ]); - - return super.serialize(); - } -} diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts deleted file mode 100644 index 4b07cbd34061..000000000000 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/SetSUCNodeIDMessages.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitOptions, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, -} 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 MessageOptions, - MessageType, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; - -export enum SetSUCNodeIdStatus { - Succeeded = 0x05, - Failed = 0x06, -} - -export interface SetSUCNodeIdRequestOptions extends MessageBaseOptions { - sucNodeId?: number; - enableSUC: boolean; - enableSIS: boolean; - transmitOptions?: TransmitOptions; -} - -@messageTypes(MessageType.Request, FunctionType.SetSUCNodeId) -@priority(MessagePriority.Controller) -export class SetSUCNodeIdRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== SetSUCNodeIdRequestStatusReport - ) { - return new SetSUCNodeIdRequestStatusReport(host, options); - } - super(host, options); - } -} - -@expectedResponse(FunctionType.SetSUCNodeId) -@expectedCallback(FunctionType.SetSUCNodeId) -export class SetSUCNodeIdRequest extends SetSUCNodeIdRequestBase { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SetSUCNodeIdRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - this.sucNodeId = options.sucNodeId ?? host.ownNodeId; - this.enableSUC = options.enableSUC; - this.enableSIS = options.enableSIS; - this.transmitOptions = options.transmitOptions - ?? TransmitOptions.DEFAULT; - } - } - - public sucNodeId: number; - public enableSUC: boolean; - public enableSIS: boolean; - public transmitOptions: TransmitOptions; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.sucNodeId, this.host.nodeIdType); - this.payload = Buffer.concat([ - nodeId, - Buffer.from([ - this.enableSUC ? 0x01 : 0x00, - this.transmitOptions, - this.enableSIS ? 0x01 : 0x00, - this.callbackId, - ]), - ]); - - return super.serialize(); - } - - public expectsCallback(): boolean { - if (this.sucNodeId === this.host.ownNodeId) return false; - return super.expectsCallback(); - } -} - -@messageTypes(MessageType.Response, FunctionType.SetSUCNodeId) -export class SetSUCNodeIdResponse extends Message implements SuccessIndicator { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this._wasExecuted = this.payload[0] !== 0; - } - - isOK(): boolean { - return this._wasExecuted; - } - - private _wasExecuted: boolean; - public get wasExecuted(): boolean { - return this._wasExecuted; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was executed": this.wasExecuted }, - }; - } -} - -export class SetSUCNodeIdRequestStatusReport extends SetSUCNodeIdRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - - this.callbackId = this.payload[0]; - this._status = this.payload[1]; - } - - private _status: SetSUCNodeIdStatus; - public get status(): SetSUCNodeIdStatus { - return this._status; - } - - public isOK(): boolean { - return this._status === SetSUCNodeIdStatus.Succeeded; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts b/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts deleted file mode 100644 index c47e36afd05d..000000000000 --- a/packages/zwave-js/src/lib/serialapi/nvm/ExtNVMWriteLongByteMessages.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { - type MessageOrCCLogEntry, - MessagePriority, - ZWaveError, - ZWaveErrorCodes, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - MessageType, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { num2hex } from "@zwave-js/shared"; - -export interface ExtNVMWriteLongByteRequestOptions extends MessageBaseOptions { - offset: number; - byte: number; -} - -@messageTypes(MessageType.Request, FunctionType.ExtExtWriteLongByte) -@priority(MessagePriority.Controller) -@expectedResponse(FunctionType.ExtExtWriteLongByte) -export class ExtNVMWriteLongByteRequest extends Message { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | ExtNVMWriteLongByteRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); - } else { - if (options.offset < 0 || options.offset > 0xffffff) { - throw new ZWaveError( - "The offset must be a 24-bit number!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - if ((options.byte & 0xff) !== options.byte) { - throw new ZWaveError( - "The data must be a byte!", - ZWaveErrorCodes.Argument_Invalid, - ); - } - this.offset = options.offset; - this.byte = options.byte; - } - } - - public offset: number; - public byte: number; - - public serialize(): Buffer { - this.payload = Buffer.allocUnsafe(4); - this.payload.writeUIntBE(this.offset, 0, 3); - this.payload[3] = this.byte; - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - offset: num2hex(this.offset), - byte: num2hex(this.byte), - }, - }; - } -} - -@messageTypes(MessageType.Response, FunctionType.ExtExtWriteLongByte) -export class ExtNVMWriteLongByteResponse extends Message { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.success = this.payload[0] !== 0; - } - - public readonly success: boolean; - - public isOK(): boolean { - return this.success; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - success: this.success, - }, - }; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts deleted file mode 100644 index 94277d88cae2..000000000000 --- a/packages/zwave-js/src/lib/serialapi/transport/SendDataBridgeMessages.ts +++ /dev/null @@ -1,481 +0,0 @@ -import type { CommandClass, ICommandClassContainer } from "@zwave-js/cc"; -import { - MAX_NODES, - type MessageOrCCLogEntry, - MessagePriority, - type MulticastCC, - type SinglecastCC, - type TXReport, - TransmitOptions, - TransmitStatus, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, -} 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 MessageOptions, - MessageType, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; -import { clamp } from "alcalzone-shared/math"; -import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; -import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; -import { MAX_SEND_ATTEMPTS } from "./SendDataMessages"; -import { parseTXReport, txReportToMessageRecord } from "./SendDataShared"; - -@messageTypes(MessageType.Request, FunctionType.SendDataBridge) -@priority(MessagePriority.Normal) -export class SendDataBridgeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== SendDataBridgeRequestTransmitReport - ) { - return new SendDataBridgeRequestTransmitReport(host, options); - } - super(host, options); - } -} - -interface SendDataBridgeRequestOptions< - CCType extends CommandClass = CommandClass, -> extends MessageBaseOptions { - command: CCType; - sourceNodeId?: number; - transmitOptions?: TransmitOptions; - maxSendAttempts?: number; -} - -@expectedResponse(FunctionType.SendDataBridge) -@expectedCallback(FunctionType.SendDataBridge) -export class SendDataBridgeRequest - extends SendDataBridgeRequestBase - implements ICommandClassContainer -{ - public constructor( - host: ZWaveHost, - options: SendDataBridgeRequestOptions, - ) { - super(host, options); - - if (!options.command.isSinglecast() && !options.command.isBroadcast()) { - throw new ZWaveError( - `SendDataBridgeRequest can only be used for singlecast and broadcast CCs`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.sourceNodeId = options.sourceNodeId ?? host.ownNodeId; - - this.command = options.command; - this.transmitOptions = options.transmitOptions - ?? TransmitOptions.DEFAULT; - if (options.maxSendAttempts != undefined) { - this.maxSendAttempts = options.maxSendAttempts; - } - } - - /** Which Node ID this command originates from */ - public sourceNodeId: number; - - /** The command this message contains */ - public command: SinglecastCC; - /** Options regarding the transmission of the message */ - public transmitOptions: TransmitOptions; - - private _maxSendAttempts: number = 1; - /** The number of times the driver may try to send this message */ - public get maxSendAttempts(): number { - return this._maxSendAttempts; - } - public set maxSendAttempts(value: number) { - this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); - } - - public override getNodeId(): number | undefined { - return this.command.nodeId; - } - - // Cache the serialized CC, so we can check if it needs to be fragmented - private _serializedCC: Buffer | undefined; - /** @internal */ - public serializeCC(): Buffer { - if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); - } - return this._serializedCC; - } - - public prepareRetransmission(): void { - this.command.prepareRetransmission(); - this._serializedCC = undefined; - this.callbackId = undefined; - } - - public serialize(): Buffer { - const sourceNodeId = encodeNodeID( - this.sourceNodeId, - this.host.nodeIdType, - ); - const destinationNodeId = encodeNodeID( - this.command.nodeId, - this.host.nodeIdType, - ); - const serializedCC = this.serializeCC(); - - this.payload = Buffer.concat([ - sourceNodeId, - destinationNodeId, - Buffer.from([serializedCC.length]), - serializedCC, - Buffer.from([this.transmitOptions, 0, 0, 0, 0, this.callbackId]), - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "source node id": this.sourceNodeId, - "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, - }, - }; - } - - public expectsNodeUpdate(): boolean { - return ( - // Only true singlecast commands may expect a response - this.command.isSinglecast() - // ... and only if the command expects a response - && this.command.expectsCCResponse() - ); - } - - public isExpectedNodeUpdate(msg: Message): boolean { - return ( - (msg instanceof ApplicationCommandRequest - || msg instanceof BridgeApplicationCommandRequest) - && this.command.isExpectedCCResponse(msg.command) - ); - } -} - -interface SendDataBridgeRequestTransmitReportOptions - extends MessageBaseOptions -{ - transmitStatus: TransmitStatus; - callbackId: number; -} - -export class SendDataBridgeRequestTransmitReport - extends SendDataBridgeRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataBridgeRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - // TODO: Consider NOT parsing this for transmit status other than OK or NoACK - this.txReport = parseTXReport( - this.transmitStatus !== TransmitStatus.NoAck, - this.payload.subarray(2), - ); - } else { - this.callbackId = options.callbackId; - this.transmitStatus = options.transmitStatus; - } - } - - public readonly transmitStatus: TransmitStatus; - public readonly txReport: TXReport | undefined; - - public isOK(): boolean { - return this.transmitStatus === TransmitStatus.OK; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "transmit status": - getEnumMemberName(TransmitStatus, this.transmitStatus) - + (this.txReport - ? `, took ${this.txReport.txTicks * 10} ms` - : ""), - ...(this.txReport - ? txReportToMessageRecord(this.txReport) - : {}), - }, - }; - } -} - -@messageTypes(MessageType.Response, FunctionType.SendDataBridge) -export class SendDataBridgeResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this._wasSent = this.payload[0] !== 0; - } - - isOK(): boolean { - return this._wasSent; - } - - private _wasSent: boolean; - public get wasSent(): boolean { - return this._wasSent; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was sent": this.wasSent }, - }; - } -} - -@messageTypes(MessageType.Request, FunctionType.SendDataMulticastBridge) -@priority(MessagePriority.Normal) -export class SendDataMulticastBridgeRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) - !== SendDataMulticastBridgeRequestTransmitReport - ) { - return new SendDataMulticastBridgeRequestTransmitReport( - host, - options, - ); - } - super(host, options); - } -} - -interface SendDataMulticastBridgeRequestOptions - extends MessageBaseOptions -{ - command: CCType; - sourceNodeId?: number; - transmitOptions?: TransmitOptions; - maxSendAttempts?: number; -} - -@expectedResponse(FunctionType.SendDataMulticastBridge) -@expectedCallback(FunctionType.SendDataMulticastBridge) -export class SendDataMulticastBridgeRequest< - CCType extends CommandClass = CommandClass, -> extends SendDataMulticastBridgeRequestBase implements ICommandClassContainer { - public constructor( - host: ZWaveHost, - options: SendDataMulticastBridgeRequestOptions, - ) { - super(host, options); - - if (!options.command.isMulticast()) { - throw new ZWaveError( - `SendDataMulticastBridgeRequest can only be used for multicast CCs`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (options.command.nodeId.length === 0) { - throw new ZWaveError( - `At least one node must be targeted`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (options.command.nodeId.some((n) => n < 1 || n > MAX_NODES)) { - throw new ZWaveError( - `All node IDs must be between 1 and ${MAX_NODES}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.sourceNodeId = options.sourceNodeId ?? host.ownNodeId; - this.command = options.command; - this.transmitOptions = options.transmitOptions - ?? TransmitOptions.DEFAULT; - if (options.maxSendAttempts != undefined) { - this.maxSendAttempts = options.maxSendAttempts; - } - } - - /** Which Node ID this command originates from */ - public sourceNodeId: number; - - /** The command this message contains */ - public command: MulticastCC; - /** Options regarding the transmission of the message */ - public transmitOptions: TransmitOptions; - - private _maxSendAttempts: number = 1; - /** The number of times the driver may try to send this message */ - public get maxSendAttempts(): number { - return this._maxSendAttempts; - } - public set maxSendAttempts(value: number) { - this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); - } - - public override getNodeId(): number | undefined { - // This is multicast, getNodeId must return undefined here - return undefined; - } - - // Cache the serialized CC, so we can check if it needs to be fragmented - private _serializedCC: Buffer | undefined; - /** @internal */ - public serializeCC(): Buffer { - if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); - } - return this._serializedCC; - } - - public prepareRetransmission(): void { - this.command.prepareRetransmission(); - this._serializedCC = undefined; - this.callbackId = undefined; - } - - public serialize(): Buffer { - const serializedCC = this.serializeCC(); - const sourceNodeId = encodeNodeID( - this.sourceNodeId, - this.host.nodeIdType, - ); - const destinationNodeIDs = this.command.nodeId.map((id) => - encodeNodeID(id, this.host.nodeIdType) - ); - - this.payload = Buffer.concat([ - sourceNodeId, - // # of target nodes, not # of bytes - Buffer.from([this.command.nodeId.length]), - ...destinationNodeIDs, - Buffer.from([serializedCC.length]), - // payload - serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "source node id": this.sourceNodeId, - "target nodes": this.command.nodeId.join(", "), - "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, - }, - }; - } -} - -interface SendDataMulticastBridgeRequestTransmitReportOptions - extends MessageBaseOptions -{ - transmitStatus: TransmitStatus; - callbackId: number; -} - -export class SendDataMulticastBridgeRequestTransmitReport - extends SendDataMulticastBridgeRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataMulticastBridgeRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this._transmitStatus = this.payload[1]; - } else { - this.callbackId = options.callbackId; - this._transmitStatus = options.transmitStatus; - } - } - - private _transmitStatus: TransmitStatus; - public get transmitStatus(): TransmitStatus { - return this._transmitStatus; - } - - public isOK(): boolean { - return this._transmitStatus === TransmitStatus.OK; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "transmit status": getEnumMemberName( - TransmitStatus, - this.transmitStatus, - ), - }, - }; - } -} - -@messageTypes(MessageType.Response, FunctionType.SendDataMulticastBridge) -export class SendDataMulticastBridgeResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this._wasSent = this.payload[0] !== 0; - } - - public isOK(): boolean { - return this._wasSent; - } - - private _wasSent: boolean; - public get wasSent(): boolean { - return this._wasSent; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was sent": this.wasSent }, - }; - } -} diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts deleted file mode 100644 index f2f7f169a4ec..000000000000 --- a/packages/zwave-js/src/lib/serialapi/transport/SendDataMessages.ts +++ /dev/null @@ -1,603 +0,0 @@ -import { CommandClass, type ICommandClassContainer } from "@zwave-js/cc"; -import { - MAX_NODES, - type MessageOrCCLogEntry, - MessagePriority, - type MulticastCC, - type MulticastDestination, - type SerializableTXReport, - type SinglecastCC, - type TXReport, - TransmitOptions, - TransmitStatus, - ZWaveError, - ZWaveErrorCodes, - encodeNodeID, - parseNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageOrigin, - MessageType, - type SuccessIndicator, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -import { getEnumMemberName, num2hex } from "@zwave-js/shared"; -import { clamp } from "alcalzone-shared/math"; -import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest"; -import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest"; -import { - encodeTXReport, - parseTXReport, - txReportToMessageRecord, -} from "./SendDataShared"; - -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) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== SendDataRequest - ) { - return new SendDataRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) !== SendDataRequestTransmitReport - ) { - return new SendDataRequestTransmitReport(host, options); - } - } - super(host, options); - } -} - -interface SendDataRequestOptions - extends MessageBaseOptions -{ - command: CCType; - transmitOptions?: TransmitOptions; - maxSendAttempts?: number; -} - -@expectedResponse(FunctionType.SendData) -@expectedCallback(FunctionType.SendData) -export class SendDataRequest - extends SendDataRequestBase - implements ICommandClassContainer -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SendDataRequestOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - let offset = 0; - const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, - offset, - ); - offset += nodeIdBytes; - this._nodeId = nodeId; - - const serializedCCLength = this.payload[offset++]; - this.transmitOptions = this.payload[offset + serializedCCLength]; - this.callbackId = this.payload[offset + 1 + serializedCCLength]; - this.payload = this.payload.subarray( - offset, - offset + serializedCCLength, - ); - - if (options.parseCCs !== false) { - this.command = CommandClass.from(host, { - nodeId, - data: this.payload, - origin: options.origin, - }) as SinglecastCC; - } else { - // Little hack for testing with a network mock. This will be parsed in the next step. - this.command = undefined as any; - } - } else { - if ( - !options.command.isSinglecast() - && !options.command.isBroadcast() - ) { - throw new ZWaveError( - `SendDataRequest can only be used for singlecast and broadcast CCs`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.command = options.command; - this._nodeId = this.command.nodeId; - this.transmitOptions = options.transmitOptions - ?? TransmitOptions.DEFAULT; - if (options.maxSendAttempts != undefined) { - this.maxSendAttempts = options.maxSendAttempts; - } - } - } - - /** The command this message contains */ - public command: SinglecastCC; - /** Options regarding the transmission of the message */ - public transmitOptions: TransmitOptions; - - private _maxSendAttempts: number = 1; - /** The number of times the driver may try to send this message */ - public get maxSendAttempts(): number { - return this._maxSendAttempts; - } - public set maxSendAttempts(value: number) { - this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); - } - - private _nodeId: number; - public override getNodeId(): number | undefined { - return this._nodeId; - } - - // Cache the serialized CC, so we can check if it needs to be fragmented - private _serializedCC: Buffer | undefined; - /** @internal */ - public serializeCC(): Buffer { - if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); - } - return this._serializedCC; - } - - public prepareRetransmission(): void { - this.command.prepareRetransmission(); - this._serializedCC = undefined; - this.callbackId = undefined; - } - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.command.nodeId, this.host.nodeIdType); - const serializedCC = this.serializeCC(); - this.payload = Buffer.concat([ - nodeId, - Buffer.from([serializedCC.length]), - serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, - }, - }; - } - - public expectsNodeUpdate(): boolean { - return ( - // Only true singlecast commands may expect a response - this.command.isSinglecast() - // ... and only if the command expects a response - && this.command.expectsCCResponse() - ); - } - - public isExpectedNodeUpdate(msg: Message): boolean { - return ( - (msg instanceof ApplicationCommandRequest - || msg instanceof BridgeApplicationCommandRequest) - && this.command.isExpectedCCResponse(msg.command) - ); - } -} - -interface SendDataRequestTransmitReportOptions extends MessageBaseOptions { - transmitStatus: TransmitStatus; - callbackId: number; - txReport?: SerializableTXReport; -} - -export class SendDataRequestTransmitReport extends SendDataRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - // TODO: Consider NOT parsing this for transmit status other than OK or NoACK - this.txReport = parseTXReport( - this.transmitStatus !== TransmitStatus.NoAck, - this.payload.subarray(2), - ); - } else { - this.callbackId = options.callbackId; - this.transmitStatus = options.transmitStatus; - this._txReport = options.txReport; - } - } - - public transmitStatus: TransmitStatus; - private _txReport: SerializableTXReport | undefined; - public txReport: TXReport | undefined; - - public serialize(): Buffer { - this.payload = Buffer.from([ - this.callbackId, - this.transmitStatus, - ]); - if (this._txReport) { - this.payload = Buffer.concat([ - this.payload, - encodeTXReport(this._txReport), - ]); - } - - return super.serialize(); - } - - public isOK(): boolean { - return this.transmitStatus === TransmitStatus.OK; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "transmit status": - getEnumMemberName(TransmitStatus, this.transmitStatus) - + (this.txReport - ? `, took ${this.txReport.txTicks * 10} ms` - : ""), - ...(this.txReport - ? txReportToMessageRecord(this.txReport) - : {}), - }, - }; - } -} - -export interface SendDataResponseOptions extends MessageBaseOptions { - wasSent: boolean; -} - -@messageTypes(MessageType.Response, FunctionType.SendData) -export class SendDataResponse extends Message implements SuccessIndicator { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SendDataResponseOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.wasSent = this.payload[0] !== 0; - } else { - this.wasSent = options.wasSent; - } - } - - public wasSent: boolean; - - public serialize(): Buffer { - this.payload = Buffer.from([this.wasSent ? 1 : 0]); - return super.serialize(); - } - - isOK(): boolean { - return this.wasSent; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was sent": this.wasSent }, - }; - } -} - -@messageTypes(MessageType.Request, FunctionType.SendDataMulticast) -@priority(MessagePriority.Normal) -export class SendDataMulticastRequestBase extends Message { - public constructor(host: ZWaveHost, options: MessageOptions) { - if (gotDeserializationOptions(options)) { - if ( - options.origin === MessageOrigin.Host - && (new.target as any) !== SendDataMulticastRequest - ) { - return new SendDataMulticastRequest(host, options); - } else if ( - options.origin !== MessageOrigin.Host - && (new.target as any) - !== SendDataMulticastRequestTransmitReport - ) { - return new SendDataMulticastRequestTransmitReport( - host, - options, - ); - } - } - - super(host, options); - } -} - -interface SendDataMulticastRequestOptions - extends MessageBaseOptions -{ - command: CCType; - transmitOptions?: TransmitOptions; - maxSendAttempts?: number; -} - -@expectedResponse(FunctionType.SendDataMulticast) -@expectedCallback(FunctionType.SendDataMulticast) -export class SendDataMulticastRequest< - CCType extends CommandClass = CommandClass, -> extends SendDataMulticastRequestBase implements ICommandClassContainer { - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataMulticastRequestOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - const numNodeIDs = this.payload[0]; - let offset = 1; - const nodeIds: number[] = []; - for (let i = 0; i < numNodeIDs; i++) { - const { nodeId, bytesRead } = parseNodeID( - this.payload, - host.nodeIdType, - offset, - ); - nodeIds.push(nodeId); - offset += bytesRead; - } - this._nodeIds = nodeIds as MulticastDestination; - - const serializedCCLength = this.payload[offset]; - offset++; - const serializedCC = this.payload.subarray( - offset, - offset + serializedCCLength, - ); - offset += serializedCCLength; - this.transmitOptions = this.payload[offset]; - offset++; - this.callbackId = this.payload[offset]; - - this.payload = serializedCC; - - if (options.parseCCs !== false) { - this.command = CommandClass.from(host, { - nodeId: this._nodeIds[0], - data: this.payload, - origin: options.origin, - }) as MulticastCC; - this.command.nodeId = this._nodeIds; - } else { - // Little hack for testing with a network mock. This will be parsed in the next step. - this.command = undefined as any; - } - } else { - if (!options.command.isMulticast()) { - throw new ZWaveError( - `SendDataMulticastRequest can only be used for multicast CCs`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if (options.command.nodeId.length === 0) { - throw new ZWaveError( - `At least one node must be targeted`, - ZWaveErrorCodes.Argument_Invalid, - ); - } else if ( - options.command.nodeId.some((n) => n < 1 || n > MAX_NODES) - ) { - throw new ZWaveError( - `All node IDs must be between 1 and ${MAX_NODES}!`, - ZWaveErrorCodes.Argument_Invalid, - ); - } - - this.command = options.command; - this.transmitOptions = options.transmitOptions - ?? TransmitOptions.DEFAULT; - if (options.maxSendAttempts != undefined) { - this.maxSendAttempts = options.maxSendAttempts; - } - } - } - - /** The command this message contains */ - public command: MulticastCC; - /** Options regarding the transmission of the message */ - public transmitOptions: TransmitOptions; - - private _maxSendAttempts: number = 1; - /** The number of times the driver may try to send this message */ - public get maxSendAttempts(): number { - return this._maxSendAttempts; - } - public set maxSendAttempts(value: number) { - this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS); - } - - private _nodeIds: MulticastDestination | undefined; - public override getNodeId(): number | undefined { - // This is multicast, getNodeId must return undefined here - return undefined; - } - - // Cache the serialized CC, so we can check if it needs to be fragmented - private _serializedCC: Buffer | undefined; - /** @internal */ - public serializeCC(): Buffer { - if (!this._serializedCC) { - this._serializedCC = this.command.serialize(); - } - return this._serializedCC; - } - - public prepareRetransmission(): void { - this.command.prepareRetransmission(); - this._serializedCC = undefined; - this.callbackId = undefined; - } - - public serialize(): Buffer { - const serializedCC = this.serializeCC(); - const destinationNodeIDs = this.command.nodeId.map((id) => - encodeNodeID(id, this.host.nodeIdType) - ); - this.payload = Buffer.concat([ - // # of target nodes, not # of bytes - Buffer.from([this.command.nodeId.length]), - ...destinationNodeIDs, - Buffer.from([serializedCC.length]), - // payload - serializedCC, - Buffer.from([this.transmitOptions, this.callbackId]), - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "target nodes": this.command.nodeId.join(", "), - "transmit options": num2hex(this.transmitOptions), - "callback id": this.callbackId, - }, - }; - } -} - -interface SendDataMulticastRequestTransmitReportOptions - extends MessageBaseOptions -{ - transmitStatus: TransmitStatus; - callbackId: number; -} - -export class SendDataMulticastRequestTransmitReport - extends SendDataMulticastRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataMulticastRequestTransmitReportOptions, - ) { - super(host, options); - - if (gotDeserializationOptions(options)) { - this.callbackId = this.payload[0]; - this._transmitStatus = this.payload[1]; - // not sure what bytes 2 and 3 mean - } else { - this.callbackId = options.callbackId; - this._transmitStatus = options.transmitStatus; - } - } - - private _transmitStatus: TransmitStatus; - public get transmitStatus(): TransmitStatus { - return this._transmitStatus; - } - - public serialize(): Buffer { - this.payload = Buffer.from([this.callbackId, this._transmitStatus]); - return super.serialize(); - } - - public isOK(): boolean { - return this._transmitStatus === TransmitStatus.OK; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "transmit status": getEnumMemberName( - TransmitStatus, - this.transmitStatus, - ), - }, - }; - } -} - -export interface SendDataMulticastResponseOptions extends MessageBaseOptions { - wasSent: boolean; -} - -@messageTypes(MessageType.Response, FunctionType.SendDataMulticast) -export class SendDataMulticastResponse extends Message - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: - | MessageDeserializationOptions - | SendDataMulticastResponseOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - this.wasSent = this.payload[0] !== 0; - } else { - this.wasSent = options.wasSent; - } - } - - public wasSent: boolean; - - public serialize(): Buffer { - this.payload = Buffer.from([this.wasSent ? 1 : 0]); - return super.serialize(); - } - - public isOK(): boolean { - return this.wasSent; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was sent": this.wasSent }, - }; - } -} - -@messageTypes(MessageType.Request, FunctionType.SendDataAbort) -@priority(MessagePriority.Controller) -export class SendDataAbort extends Message {} diff --git a/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts b/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts deleted file mode 100644 index feea59a1c3e1..000000000000 --- a/packages/zwave-js/src/lib/serialapi/transport/SendTestFrameMessages.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Powerlevel } from "@zwave-js/cc"; -import { - type MessageOrCCLogEntry, - MessagePriority, - TransmitStatus, - encodeNodeID, - parseNodeID, -} from "@zwave-js/core"; -import type { ZWaveHost } from "@zwave-js/host"; -import { - FunctionType, - Message, - type MessageBaseOptions, - type MessageDeserializationOptions, - type MessageOptions, - MessageType, - type SuccessIndicator, - expectedCallback, - expectedResponse, - gotDeserializationOptions, - messageTypes, - priority, -} from "@zwave-js/serial"; -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) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== SendTestFrameTransmitReport - ) { - return new SendTestFrameTransmitReport(host, options); - } - super(host, options); - } -} - -export interface SendTestFrameRequestOptions extends MessageBaseOptions { - testNodeId: number; - powerlevel: Powerlevel; -} - -@expectedResponse(FunctionType.SendTestFrame) -@expectedCallback(FunctionType.SendTestFrame) -export class SendTestFrameRequest extends SendTestFrameRequestBase { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions | SendTestFrameRequestOptions, - ) { - super(host, options); - if (gotDeserializationOptions(options)) { - let offset = 0; - const { nodeId, bytesRead: nodeIdBytes } = parseNodeID( - this.payload, - host.nodeIdType, - offset, - ); - offset += nodeIdBytes; - this.testNodeId = nodeId; - - this.powerlevel = this.payload[offset++]; - this.callbackId = this.payload[offset++]; - } else { - this.testNodeId = options.testNodeId; - this.powerlevel = options.powerlevel; - } - } - - public testNodeId: number; - public powerlevel: Powerlevel; - - public serialize(): Buffer { - const nodeId = encodeNodeID(this.testNodeId, this.host.nodeIdType); - this.payload = Buffer.concat([ - nodeId, - Buffer.from([ - this.powerlevel, - this.callbackId, - ]), - ]); - - return super.serialize(); - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "test node id": this.testNodeId, - powerlevel: getEnumMemberName(Powerlevel, this.powerlevel), - "callback id": this.callbackId, - }, - }; - } -} - -@messageTypes(MessageType.Response, FunctionType.SendTestFrame) -export class SendTestFrameResponse extends Message { - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - this.wasSent = this.payload[0] !== 0; - } - - public readonly wasSent: boolean; - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { "was sent": this.wasSent }, - }; - } -} - -export class SendTestFrameTransmitReport extends SendTestFrameRequestBase - implements SuccessIndicator -{ - public constructor( - host: ZWaveHost, - options: MessageDeserializationOptions, - ) { - super(host, options); - - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; - } - - public transmitStatus: TransmitStatus; - - isOK(): boolean { - return this.transmitStatus === TransmitStatus.OK; - } - - public toLogEntry(): MessageOrCCLogEntry { - return { - ...super.toLogEntry(), - message: { - "callback id": this.callbackId, - "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/assertCC.ts b/packages/zwave-js/src/lib/test/assertCC.ts index 458f29adcafb..69f02f92d264 100644 --- a/packages/zwave-js/src/lib/test/assertCC.ts +++ b/packages/zwave-js/src/lib/test/assertCC.ts @@ -1,7 +1,7 @@ import type { CCConstructor, CommandClass } from "@zwave-js/cc"; +import { SendDataBridgeRequest } from "@zwave-js/serial/serialapi"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; import type { ExecutionContext } from "ava"; -import { SendDataBridgeRequest } from "../serialapi/transport/SendDataBridgeMessages"; -import { SendDataRequest } from "../serialapi/transport/SendDataMessages"; export function assertCC< TConst extends CCConstructor = CCConstructor, 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..5acfc308b636 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,15 +37,15 @@ 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, - endpoint: 1, + cc = new MultiChannelCCCommandEncapsulation({ + nodeId: mockController.ownNodeId, + endpointIndex: 1, destination: 0, encapsulated: cc, }); @@ -56,15 +56,15 @@ 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, - endpoint: 1, + cc = new MultiChannelCCCommandEncapsulation({ + nodeId: mockController.ownNodeId, + endpointIndex: 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..dd74763c5d38 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationCC.test.ts @@ -6,13 +6,11 @@ import { AssociationCCSupportedGroupingsGet, AssociationCCSupportedGroupingsReport, AssociationCommand, + CommandClass, } 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 +21,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 +29,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 +39,17 @@ test("the SupportedGroupingsReport command should be deserialized correctly", (t 7, // # of groups ]), ); - const cc = new AssociationCCSupportedGroupingsReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as AssociationCCSupportedGroupingsReport; + t.is(cc.constructor, AssociationCCSupportedGroupingsReport); 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 +64,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 +77,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,10 +93,11 @@ test("the Report command should be deserialized correctly", (t) => { 5, ]), ); - const cc = new AssociationCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as AssociationCCReport; + t.is(cc.constructor, AssociationCCReport); t.is(cc.groupId, 5); t.is(cc.maxNodes, 9); @@ -106,7 +106,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 +121,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 +135,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 +143,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..3e924a7af981 100644 --- a/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/AssociationGroupInfoCC.test.ts @@ -9,13 +9,11 @@ import { AssociationGroupInfoCommand, AssociationGroupInfoProfile, BasicCommand, + CommandClass, } 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 +24,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 +34,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,17 +52,18 @@ test("the NameReport command should be deserialized correctly", (t) => { 0x72, ]), ); - const cc = new AssociationGroupInfoCCNameReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as AssociationGroupInfoCCNameReport; + t.is(cc.constructor, AssociationGroupInfoCCNameReport); t.is(cc.groupId, 7); t.is(cc.name, "foobar"); }); 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 +76,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 +93,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 +110,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,10 +139,11 @@ test("the Info Report command should be deserialized correctly", (t) => { 0, ]), ); - const cc = new AssociationGroupInfoCCInfoReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as AssociationGroupInfoCCInfoReport; + t.is(cc.constructor, AssociationGroupInfoCCInfoReport); t.is(cc.groups.length, 2); t.is(cc.groups[0].groupId, 1); @@ -156,7 +156,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 +168,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,10 +185,11 @@ test("the CommandListReport command should be deserialized correctly", (t) => { 0x05, ]), ); - const cc = new AssociationGroupInfoCCCommandListReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as AssociationGroupInfoCCCommandListReport; + t.is(cc.constructor, AssociationGroupInfoCCCommandListReport); t.is(cc.groupId, 7); t.is(cc.commands.size, 2); @@ -203,10 +204,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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as AssociationGroupInfoCC; 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..84c702d7648d 100644 --- a/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BasicCC.test.ts @@ -5,13 +5,14 @@ import { BasicCCSet, type BasicCCValues, BasicCommand, + CommandClass, getCCValues, } from "@zwave-js/cc"; 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 +26,31 @@ 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 Get command should be deserialized correctly", (t) => { + const ccData = buildCCBuffer( + Buffer.from([ + BasicCommand.Get, // CC Command + ]), + ); + const basicCC = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as BasicCCGet; + t.is(basicCC.constructor, BasicCCGet); + t.is(basicCC.nodeId, 2); }); test("the Set command should serialize correctly", (t) => { - const basicCC = new BasicCCSet(host, { + const basicCC = new BasicCCSet({ nodeId: 2, targetValue: 55, }); @@ -45,7 +60,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,10 +70,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 55, // current value ]), ); - const basicCC = new BasicCCReport(host, { - nodeId: 2, - data: ccData, - }); + const basicCC = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as BasicCCReport; + t.is(basicCC.constructor, BasicCCReport); t.is(basicCC.currentValue, 55); t.is(basicCC.targetValue, undefined); @@ -74,10 +90,11 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // duration ]), ); - const basicCC = new BasicCCReport(host, { - nodeId: 2, - data: ccData, - }); + const basicCC = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as BasicCCReport; + t.is(basicCC.constructor, BasicCCReport); t.is(basicCC.currentValue, 55); t.is(basicCC.targetValue, 66); @@ -89,38 +106,32 @@ 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, { - nodeId: 2, - data: serializedCC, - }); + const basicCC = CommandClass.parse( + serializedCC, + { sourceNodeId: 2 } as any, + ) as BasicCCReport; t.is(basicCC.constructor, BasicCC); }); test("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,21 +145,21 @@ 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, + endpointIndex: 2, targetValue: 7, }); t.false(cc.expectsCCResponse()); }); test("BasicCCSet => BasicCCReport = unexpected", (t) => { - const ccRequest = new BasicCCSet(host, { + const ccRequest = new BasicCCSet({ nodeId: 2, - endpoint: 2, + endpointIndex: 2, targetValue: 7, }); - const ccResponse = new BasicCCReport(host, { + const ccResponse = new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }); @@ -157,17 +168,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 +187,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 +199,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 +211,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..d2efa97ee728 100644 --- a/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BatteryCC.test.ts @@ -1,24 +1,22 @@ import { BatteryCC, BatteryCCGet, - type BatteryCCReport, + BatteryCCReport, BatteryChargingStatus, BatteryCommand, BatteryReplacementStatus, + CommandClass, } 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,10 +25,11 @@ test("the Report command (v1) should be deserialized correctly: when the battery BatteryCommand.Report, // CC Command 55, // current value ]); - const batteryCC = new BatteryCC(host, { - nodeId: 7, - data: ccData, - }) as BatteryCCReport; + const batteryCC = CommandClass.parse( + ccData, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCCReport); t.is(batteryCC.level, 55); t.false(batteryCC.isLow); @@ -42,10 +41,11 @@ test("the Report command (v1) should be deserialized correctly: when the battery BatteryCommand.Report, // CC Command 0xff, // current value ]); - const batteryCC = new BatteryCC(host, { - nodeId: 7, - data: ccData, - }) as BatteryCCReport; + const batteryCC = CommandClass.parse( + ccData, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCCReport); t.is(batteryCC.level, 0); t.true(batteryCC.isLow); @@ -59,10 +59,11 @@ test("the Report command (v2) should be deserialized correctly: all flags set", 0b00_1111_00, 1, // disconnected ]); - const batteryCC = new BatteryCC(host, { - nodeId: 7, - data: ccData, - }) as BatteryCCReport; + const batteryCC = CommandClass.parse( + ccData, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCCReport); t.true(batteryCC.rechargeable); t.true(batteryCC.backup); @@ -79,10 +80,11 @@ test("the Report command (v2) should be deserialized correctly: charging status" 0b10_000000, // Maintaining 0, ]); - const batteryCC = new BatteryCC(host, { - nodeId: 7, - data: ccData, - }) as BatteryCCReport; + const batteryCC = CommandClass.parse( + ccData, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCCReport); t.is(batteryCC.chargingStatus, BatteryChargingStatus.Maintaining); }); @@ -95,10 +97,11 @@ test("the Report command (v2) should be deserialized correctly: recharge or repl 0b11, // Maintaining 0, ]); - const batteryCC = new BatteryCC(host, { - nodeId: 7, - data: ccData, - }) as BatteryCCReport; + const batteryCC = CommandClass.parse( + ccData, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCCReport); t.is(batteryCC.rechargeOrReplace, BatteryReplacementStatus.Now); }); @@ -108,11 +111,11 @@ test("deserializing an unsupported command should return an unspecified version CommandClasses.Battery, // CC 255, // not a valid command ]); - const basicCC: any = new BatteryCC(host, { - nodeId: 7, - data: serializedCC, - }); - t.is(basicCC.constructor, BatteryCC); + const batteryCC = CommandClass.parse( + serializedCC, + { sourceNodeId: 7 } as any, + ) as BatteryCCReport; + t.is(batteryCC.constructor, BatteryCC); }); // describe.skip(`interview()`, () => { 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..08320b741ea4 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySensorCC.test.ts @@ -6,13 +6,11 @@ import { BinarySensorCCSupportedReport, BinarySensorCommand, BinarySensorType, + CommandClass, } 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 +21,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,10 +49,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0xff, // current value ]), ); - const cc = new BinarySensorCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as BinarySensorCCReport; + t.is(cc.constructor, BinarySensorCCReport); t.is(cc.value, true); }); @@ -67,23 +66,24 @@ test("the Report command (v2) should be deserialized correctly", (t) => { BinarySensorType.CO2, ]), ); - const cc = new BinarySensorCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as BinarySensorCCReport; + t.is(cc.constructor, BinarySensorCCReport); t.is(cc.value, false); t.is(cc.type, BinarySensorType.CO2); }); 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,10 +94,11 @@ test("the SupportedReport command should be deserialized correctly", (t) => { 0b10, ]), ); - const cc = new BinarySensorCCSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as BinarySensorCCSupportedReport; + t.is(cc.constructor, BinarySensorCCSupportedReport); t.deepEqual(cc.supportedSensorTypes, [ BinarySensorType["General Purpose"], @@ -112,10 +113,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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as BinarySensorCC; 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..755c4fa39760 100644 --- a/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/BinarySwitchCC.test.ts @@ -4,13 +4,12 @@ import { BinarySwitchCCReport, BinarySwitchCCSet, BinarySwitchCommand, + CommandClass, } 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 +20,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 +41,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 +64,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,10 +80,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0xff, // current value ]), ); - const cc = new BinarySwitchCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as BinarySwitchCCReport; + t.is(cc.constructor, BinarySwitchCCReport); t.is(cc.currentValue, true); t.is(cc.targetValue, undefined); @@ -90,10 +100,11 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // duration ]), ); - const cc = new BinarySwitchCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as BinarySwitchCCReport; + t.is(cc.constructor, BinarySwitchCCReport); t.is(cc.currentValue, true); t.is(cc.targetValue, false); @@ -105,10 +116,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, { - nodeId: 2, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 2 } as any, + ) as BinarySwitchCC; 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..027a74774a00 100644 --- a/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CRC16CC.test.ts @@ -7,43 +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, { - nodeId: basicCCSet.nodeId as number, - data: serialized, - }); + const deserialized = CommandClass.parse( + serialized, + { sourceNodeId: basicCCSet.nodeId as number } as any, + ); t.is(deserialized.nodeId, basicCCSet.nodeId); const deserializedPayload = (deserialized as CRC16CCCommandEncapsulation) .encapsulated as BasicCCSet; @@ -53,19 +50,19 @@ 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, { - nodeId: basicCCSet.nodeId as number, - data: serialized, - }); - t.true(decoded instanceof InvalidCC); + const deserialized = CommandClass.parse( + serialized, + { sourceNodeId: basicCCSet.nodeId as number } as any, + ); + t.true(deserialized 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..3bdb279111b4 100644 --- a/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CentralSceneCC.test.ts @@ -8,13 +8,11 @@ import { CentralSceneCCSupportedReport, CentralSceneCommand, CentralSceneKeys, + CommandClass, } 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 +23,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 +31,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 +45,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 +59,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 +69,17 @@ test("the ConfigurationReport command should be deserialized correctly", (t) => 0b1000_0000, ]), ); - const cc = new CentralSceneCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as CentralSceneCCConfigurationReport; + t.is(cc.constructor, CentralSceneCCConfigurationReport); 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 +87,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,10 +102,11 @@ test("the SupportedReport command should be deserialized correctly", (t) => { 0, ]), ); - const cc = new CentralSceneCCSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as CentralSceneCCSupportedReport; + t.is(cc.constructor, CentralSceneCCSupportedReport); t.is(cc.sceneCount, 2); t.true(cc.supportsSlowRefresh); @@ -125,10 +125,11 @@ test("the Notification command should be deserialized correctly", (t) => { 8, // scene number ]), ); - const cc = new CentralSceneCCNotification(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as CentralSceneCCNotification; + t.is(cc.constructor, CentralSceneCCNotification); t.is(cc.sequenceNumber, 7); // slow refresh is only evaluated if the attribute is KeyHeldDown @@ -146,10 +147,11 @@ test("the Notification command should be deserialized correctly (KeyHeldDown)", 8, // scene number ]), ); - const cc = new CentralSceneCCNotification(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as CentralSceneCCNotification; + t.is(cc.constructor, CentralSceneCCNotification); t.is(cc.sequenceNumber, 7); t.true(cc.slowRefresh); @@ -161,10 +163,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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as CentralSceneCC; 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..da917686d044 100644 --- a/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ColorSwitchCC.test.ts @@ -9,6 +9,7 @@ import { ColorSwitchCCSupportedGet, ColorSwitchCCSupportedReport, ColorSwitchCommand, + CommandClass, } from "@zwave-js/cc"; import { CommandClasses, @@ -16,11 +17,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 +30,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 +38,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,10 +49,11 @@ test("the SupportedReport command should deserialize correctly", (t) => { 0b0000_0001, ]), ); - const cc = new ColorSwitchCCSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as ColorSwitchCCSupportedReport; + t.is(cc.constructor, ColorSwitchCCSupportedReport); t.deepEqual(cc.supportedColorComponents, [ ColorComponent["Warm White"], @@ -71,7 +71,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 +81,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,10 +92,11 @@ test("the Report command should deserialize correctly (version 1)", (t) => { 0b1111_1111, // value: 255 ]), ); - const cc = new ColorSwitchCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as ColorSwitchCCReport; + t.is(cc.constructor, ColorSwitchCCReport); t.is(cc.colorComponent, ColorComponent.Red); t.is(cc.currentValue, 255); @@ -113,10 +114,11 @@ test("the Report command should deserialize correctly (version 3)", (t) => { 0b0000_0001, // duration: 1 ]), ); - const cc = new ColorSwitchCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as ColorSwitchCCReport; + t.is(cc.constructor, ColorSwitchCCReport); t.is(cc.colorComponent, ColorComponent.Red); t.is(cc.currentValue, 128); @@ -126,7 +128,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 +146,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 +176,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 +194,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 +203,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 +224,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..81ed93adca18 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 @@ -1,4 +1,4 @@ -import { CentralSceneCommand, CentralSceneKeys } from "@zwave-js/cc"; +import { CentralSceneKeys } from "@zwave-js/cc"; import { BasicCCSet } from "@zwave-js/cc/BasicCC"; import { CentralSceneCCNotification } from "@zwave-js/cc/CentralSceneCC"; import { CommandClasses } from "@zwave-js/core"; @@ -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,15 +91,11 @@ 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"], - CentralSceneCommand.Notification, - 1, // seq number - CentralSceneKeys.KeyPressed, - 1, // scene number - ]), + sequenceNumber: 1, + sceneNumber: 1, + keyAttribute: CentralSceneKeys.KeyPressed, }); // 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..2c0c532cc1da 100644 --- a/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts +++ b/packages/zwave-js/src/lib/test/cc/CommandClass.test.ts @@ -10,11 +10,8 @@ import { implementedVersion, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; -import { createTestingHost } from "@zwave-js/host"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; import test from "ava"; -import { SendDataRequest } from "../../serialapi/transport/SendDataMessages"; - -const host = createTestingHost(); @implementedVersion(7) @commandClass(0xffff as any) @@ -29,28 +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) => { +test("parse() 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, { - data: Buffer.from("78030100", "hex"), - nodeId: 5, - }); + const cc = CommandClass.parse( + Buffer.from("78030100", "hex"), + { sourceNodeId: 5 } as any, + ); t.is(cc.constructor, CommandClass); t.is(cc.nodeId, 5); t.is(cc.ccId, 0x78); @@ -58,18 +55,18 @@ test("from() returns an un-specialized instance when receiving a non-implemented t.deepEqual(cc.payload, Buffer.from([0x01, 0x00])); }); -test("from() does not throw when the CC is implemented", (t) => { +test("parse() does not throw when the CC is implemented", (t) => { t.notThrows(() => - CommandClass.from(host, { - // CRC-16 with BasicCC - data: Buffer.from("560120024d26", "hex"), - nodeId: 5, - }) + // CRC-16 with BasicCC + CommandClass.parse( + Buffer.from("560120024d26", "hex"), + { sourceNodeId: 5 } 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 +77,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 +94,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..3167154c3c41 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, DoorLockCCCapabilitiesGet, DoorLockCCCapabilitiesReport, DoorLockCCConfigurationGet, @@ -40,17 +41,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 +61,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,10 +75,11 @@ test("the OperationReport command (v1-v3) should be deserialized correctly", (t) 20, // timeout seconds ]), ); - const cc = new DoorLockCCOperationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCOperationReport; + t.is(cc.constructor, DoorLockCCOperationReport); t.is(cc.currentMode, DoorLockMode.InsideUnsecuredWithTimeout); t.deepEqual(cc.outsideHandlesCanOpenDoor, [false, false, false, true]); @@ -103,10 +105,11 @@ test("the OperationReport command (v4) should be deserialized correctly", (t) => 0x01, // 1 second left ]), ); - const cc = new DoorLockCCOperationReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as DoorLockCCOperationReport; + t.is(cc.constructor, DoorLockCCOperationReport); cc.persistValues(host); t.is(cc.currentMode, DoorLockMode.OutsideUnsecured); @@ -128,13 +131,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,10 +150,11 @@ test("the ConfigurationReport command (v1-v3) should be deserialized correctly", 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCConfigurationReport; + t.is(cc.constructor, DoorLockCCConfigurationReport); t.is(cc.operationType, DoorLockOperationType.Timed); t.deepEqual(cc.outsideHandlesCanOpenDoorConfiguration, [ @@ -182,10 +186,11 @@ test("the ConfigurationReport command must ignore invalid timeouts (constant)", 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCConfigurationReport; + t.is(cc.constructor, DoorLockCCConfigurationReport); t.is(cc.lockTimeoutConfiguration, undefined); }); @@ -200,10 +205,11 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid minu 20, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCConfigurationReport; + t.is(cc.constructor, DoorLockCCConfigurationReport); t.is(cc.lockTimeoutConfiguration, undefined); }); @@ -218,10 +224,11 @@ test("the ConfigurationReport command must ignore invalid timeouts (invalid seco 0xff, // timeout seconds ]), ); - const cc = new DoorLockCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCConfigurationReport; + t.is(cc.constructor, DoorLockCCConfigurationReport); t.is(cc.lockTimeoutConfiguration, undefined); }); @@ -242,10 +249,11 @@ test("the ConfigurationReport command (v4) should be deserialized correctly", (t 0b01, // flags ]), ); - const cc = new DoorLockCCConfigurationReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCConfigurationReport; + t.is(cc.constructor, DoorLockCCConfigurationReport); t.is(cc.autoRelockTime, 0xff01); t.is(cc.holdAndReleaseTime, 0x0203); @@ -254,7 +262,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 +287,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,10 +315,11 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { 0b1010, // feature flags ]), ); - const cc = new DoorLockCCCapabilitiesReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockCCCapabilitiesReport; + t.is(cc.constructor, DoorLockCCCapabilitiesReport); t.deepEqual(cc.supportedOperationTypes, [ DoorLockOperationType.Constant, @@ -341,7 +350,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 +363,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..f6a783a63637 100644 --- a/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/DoorLockLoggingCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, DoorLockLoggingCCRecordGet, DoorLockLoggingCCRecordReport, DoorLockLoggingCCRecordsSupportedGet, @@ -7,11 +8,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 +20,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 +28,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 +38,17 @@ test("the RecordsCountReport command should be deserialized correctly", (t) => { 0x14, // max records supported (20) ]), ); - const cc = new DoorLockLoggingCCRecordsSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockLoggingCCRecordsSupportedReport; + t.is(cc.constructor, DoorLockLoggingCCRecordsSupportedReport); 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 +58,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,10 +80,11 @@ test("the RecordReport command should be deserialized correctly", (t) => { ]), ); - const cc = new DoorLockLoggingCCRecordReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as DoorLockLoggingCCRecordReport; + t.is(cc.constructor, DoorLockLoggingCCRecordReport); 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..6c2de3081d77 100644 --- a/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/EntryControlCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, EntryControlCCConfigurationGet, EntryControlCCConfigurationReport, EntryControlCCConfigurationSet, @@ -12,11 +13,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,10 +43,11 @@ test("the Notification command should deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCNotification(host, { - nodeId: 1, + const cc = CommandClass.parse( data, - }); + { sourceNodeId: 1 } as any, + ) as EntryControlCCNotification; + t.is(cc.constructor, EntryControlCCNotification); t.deepEqual(cc.sequenceNumber, 1); t.deepEqual(cc.dataType, EntryControlDataTypes.ASCII); @@ -57,7 +56,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 +64,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 +80,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,17 +92,18 @@ test("the ConfigurationReport command should be deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCConfigurationReport(host, { - nodeId: 1, + const cc = CommandClass.parse( data, - }); + { sourceNodeId: 1 } as any, + ) as EntryControlCCConfigurationReport; + t.is(cc.constructor, EntryControlCCConfigurationReport); t.deepEqual(cc.keyCacheSize, 1); t.deepEqual(cc.keyCacheTimeout, 2); }); test("the EventSupportedGet command should serialize correctly", (t) => { - const cc = new EntryControlCCEventSupportedGet(host, { + const cc = new EntryControlCCEventSupportedGet({ nodeId: 1, }); const expected = buildCCBuffer( @@ -111,7 +111,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,10 +132,11 @@ test("the EventSupportedReport command should be deserialize correctly", (t) => ]), ); - const cc = new EntryControlCCEventSupportedReport(host, { - nodeId: 1, + const cc = CommandClass.parse( data, - }); + { sourceNodeId: 1 } as any, + ) as EntryControlCCEventSupportedReport; + t.is(cc.constructor, EntryControlCCEventSupportedReport); t.deepEqual(cc.supportedDataTypes, [EntryControlDataTypes.ASCII]); t.deepEqual(cc.supportedEventTypes, [ @@ -151,13 +152,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,10 +170,11 @@ test("the KeySupportedReport command should be deserialize correctly", (t) => { ]), ); - const cc = new EntryControlCCKeySupportedReport(host, { - nodeId: 1, + const cc = CommandClass.parse( data, - }); + { sourceNodeId: 1 } as any, + ) as EntryControlCCKeySupportedReport; + t.is(cc.constructor, EntryControlCCKeySupportedReport); 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..bb8fe9bc9488 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,17 +47,17 @@ test("the Report command should be deserialized correctly", (t) => { 0x00, // Tilt ]), ); - const cc = CommandClass.from(host, { - nodeId: 2, - data: ccData, - }); - t.true(cc instanceof FibaroVenetianBlindCCReport); - t.is((cc as FibaroVenetianBlindCCReport).position, 0); - t.is((cc as FibaroVenetianBlindCCReport).tilt, 0); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as FibaroVenetianBlindCCReport; + t.is(cc.constructor, FibaroVenetianBlindCCReport); + t.is(cc.position, 0); + t.is(cc.tilt, 0); }); test("FibaroVenetianBlindCCSet should expect no response", (t) => { - const cc = new FibaroVenetianBlindCCSet(host, { + const cc = new FibaroVenetianBlindCCSet({ nodeId: 2, tilt: 7, }); @@ -68,20 +65,19 @@ 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, { - nodeId: 2, - data: buildCCBuffer( + const ccResponse = CommandClass.parse( + buildCCBuffer( Buffer.from([ FibaroVenetianBlindCCCommand.Report, 0x03, // with Tilt and Position @@ -89,18 +85,18 @@ test("FibaroVenetianBlindCCSet => FibaroVenetianBlindCCReport = unexpected", (t) 0x07, // Tilt ]), ), - }); + { sourceNodeId: 2 } as any, + ) as FibaroVenetianBlindCCReport; 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, { - nodeId: 2, - data: buildCCBuffer( + const ccResponse = CommandClass.parse( + buildCCBuffer( Buffer.from([ FibaroVenetianBlindCCCommand.Report, 0x03, // with Tilt and Position @@ -108,7 +104,8 @@ test("FibaroVenetianBlindCCGet => FibaroVenetianBlindCCReport = expected", (t) = 0x07, // Tilt ]), ), - }); + { sourceNodeId: 2 } as any, + ) as FibaroVenetianBlindCCReport; 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..f859eb75dd44 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlModeCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, HumidityControlMode, HumidityControlModeCCGet, HumidityControlModeCCReport, @@ -25,7 +26,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 +34,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 +48,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,10 +58,11 @@ test("the Report command should be deserialized correctly", (t) => { HumidityControlMode.Auto, // current value ]), ); - const cc = new HumidityControlModeCCReport(host, { - nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlModeCCReport; + t.is(cc.constructor, HumidityControlModeCCReport); t.is(cc.mode, HumidityControlMode.Auto); }); @@ -72,10 +74,10 @@ test("the Report command should set the correct value", (t) => { HumidityControlMode.Auto, // current value ]), ); - const report = new HumidityControlModeCCReport(host, { - nodeId, - data: ccData, - }); + const report = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlModeCCReport; report.persistValues(host); const currentValue = host.getValueDB(nodeId).getValue({ @@ -92,10 +94,10 @@ test("the Report command should set the correct metadata", (t) => { HumidityControlMode.Auto, // current value ]), ); - const cc = new HumidityControlModeCCReport(host, { - nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlModeCCReport; cc.persistValues(host); const currentValueMeta = host @@ -109,7 +111,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 +119,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,10 +129,11 @@ test("the SupportedReport command should be deserialized correctly", (t) => { (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), ); - const cc = new HumidityControlModeCCSupportedReport(host, { - nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlModeCCSupportedReport; + t.is(cc.constructor, HumidityControlModeCCSupportedReport); t.deepEqual(cc.supportedModes, [ HumidityControlMode.Off, @@ -145,10 +148,10 @@ test("the SupportedReport command should set the correct metadata", (t) => { (1 << HumidityControlMode.Off) | (1 << HumidityControlMode.Auto), ]), ); - const cc = new HumidityControlModeCCSupportedReport(host, { - nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlModeCCSupportedReport; cc.persistValues(host); const currentValueMeta = 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..ab4b15c30b97 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlOperatingStateCC.test.ts @@ -1,15 +1,13 @@ import { + CommandClass, HumidityControlOperatingState, HumidityControlOperatingStateCCGet, HumidityControlOperatingStateCCReport, 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 +18,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 +26,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,10 +36,11 @@ test("the Report command should be deserialized correctly", (t) => { HumidityControlOperatingState.Humidifying, // state ]), ); - const cc = new HumidityControlOperatingStateCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as HumidityControlOperatingStateCCReport; + t.is(cc.constructor, HumidityControlOperatingStateCCReport); 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..56c0de1fe87d 100644 --- a/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/HumidityControlSetpointCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, HumidityControlSetpointCCCapabilitiesGet, HumidityControlSetpointCCCapabilitiesReport, HumidityControlSetpointCCGet, @@ -28,7 +29,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 +39,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 +58,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,10 +71,11 @@ test("the Report command should be deserialized correctly", (t) => { encodeFloatWithScale(12, 1), ]), ); - const cc = new HumidityControlSetpointCCReport(host, { - nodeId: nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCReport; + t.is(cc.constructor, HumidityControlSetpointCCReport); t.deepEqual(cc.type, HumidityControlSetpointType.Humidifier); t.is(cc.scale, 1); @@ -95,10 +97,10 @@ test("the Report command should set the correct value", (t) => { encodeFloatWithScale(12, 1), ]), ); - const report = new HumidityControlSetpointCCReport(host, { - nodeId: nodeId, - data: ccData, - }); + const report = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCReport; report.persistValues(host); const currentValue = host.getValueDB(nodeId).getValue({ @@ -126,10 +128,10 @@ test("the Report command should set the correct metadata", (t) => { encodeFloatWithScale(12, 1), ]), ); - const report = new HumidityControlSetpointCCReport(host, { - nodeId: nodeId, - data: ccData, - }); + const report = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCReport; report.persistValues(host); const setpointMeta = host.getValueDB(nodeId).getMetadata({ @@ -146,7 +148,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 +156,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,10 +167,11 @@ test("the SupportedReport command should be deserialized correctly", (t) => { | (1 << HumidityControlSetpointType.Auto), ]), ); - const cc = new HumidityControlSetpointCCSupportedReport(host, { - nodeId: nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCSupportedReport; + t.is(cc.constructor, HumidityControlSetpointCCSupportedReport); t.deepEqual(cc.supportedSetpointTypes, [ HumidityControlSetpointType.Humidifier, @@ -184,10 +187,10 @@ test("the SupportedReport command should set the correct value", (t) => { | (1 << HumidityControlSetpointType.Auto), ]), ); - const report = new HumidityControlSetpointCCSupportedReport(host, { - nodeId: nodeId, - data: ccData, - }); + const report = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCSupportedReport; report.persistValues(host); const currentValue = host.getValueDB(nodeId).getValue({ @@ -201,7 +204,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 +214,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,10 +224,11 @@ test("the ScaleSupportedReport command should be deserialized correctly", (t) => 0b11, // percent + absolute ]), ); - const cc = new HumidityControlSetpointCCScaleSupportedReport(host, { - nodeId: nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCScaleSupportedReport; + t.is(cc.constructor, HumidityControlSetpointCCScaleSupportedReport); t.deepEqual(cc.supportedScales, [0, 1]); // new Scale(0, { @@ -239,7 +243,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 +253,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,10 +267,11 @@ test("the CapabilitiesReport command should be deserialized correctly", (t) => { encodeFloatWithScale(90, 1), ]), ); - const cc = new HumidityControlSetpointCCCapabilitiesReport(host, { - nodeId: nodeId, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCCapabilitiesReport; + t.is(cc.constructor, HumidityControlSetpointCCCapabilitiesReport); t.deepEqual(cc.type, HumidityControlSetpointType.Humidifier); t.deepEqual(cc.minValue, 10); @@ -286,10 +291,10 @@ test("the CapabilitiesReport command should set the correct metadata", (t) => { encodeFloatWithScale(90, 1), ]), ); - const report = new HumidityControlSetpointCCCapabilitiesReport(host, { - nodeId: nodeId, - data: ccData, - }); + const report = CommandClass.parse( + ccData, + { sourceNodeId: nodeId } as any, + ) as HumidityControlSetpointCCCapabilitiesReport; report.persistValues(host); const setpointMeta = host.getValueDB(nodeId).getMetadata({ 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..bd6e42323bdb 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,10 +100,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 55, // value ]), ); - const cc = new IndicatorCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as IndicatorCCReport; + t.is(cc.constructor, IndicatorCCReport); t.is(cc.indicator0Value, 55); t.is(cc.values, undefined); @@ -123,10 +124,11 @@ test("the Report command (v2) should be deserialized correctly", (t) => { 1, // value ]), ); - const cc = new IndicatorCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as IndicatorCCReport; + t.is(cc.constructor, IndicatorCCReport); // Boolean indicators are only interpreted during persistValues cc.persistValues(host); @@ -149,10 +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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as IndicatorCC; t.is(cc.constructor, IndicatorCC); }); @@ -160,7 +162,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..434a6309e9bd 100644 --- a/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/LanguageCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, LanguageCC, LanguageCCGet, LanguageCCReport, @@ -6,11 +7,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 +19,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 +42,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 +63,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,10 +76,11 @@ test("the Report command should be deserialized correctly (w/o country code)", ( 0x75, ]), ); - const cc = new LanguageCCReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as LanguageCCReport; + t.is(cc.constructor, LanguageCCReport); t.is(cc.language, "deu"); t.is(cc.country, undefined); @@ -100,10 +99,11 @@ test("the Report command should be deserialized correctly (w/ country code)", (t 0x45, ]), ); - const cc = new LanguageCCReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as LanguageCCReport; + t.is(cc.constructor, LanguageCCReport); t.is(cc.language, "deu"); t.is(cc.country, "DE"); @@ -113,10 +113,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, { - nodeId: 4, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 4 } as any, + ) as LanguageCC; 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..111562efa31e 100644 --- a/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ManufacturerSpecificCC.test.ts @@ -1,14 +1,12 @@ import { + CommandClass, ManufacturerSpecificCCGet, ManufacturerSpecificCCReport, 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 +17,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,10 +38,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x06, ]), ); - const cc = new ManufacturerSpecificCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as ManufacturerSpecificCCReport; + t.is(cc.constructor, ManufacturerSpecificCCReport); t.is(cc.manufacturerId, 0x0102); t.is(cc.productType, 0x0304); 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..14806e0a3181 100644 --- a/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MeterCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, MeterCC, MeterCCGet, MeterCCReport, @@ -13,7 +14,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 +32,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 +90,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 +135,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 +147,11 @@ test("the Report command (V1) should be deserialized correctly", (t) => { 55, // value ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(cc.constructor, MeterCCReport); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -143,7 +172,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 = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(cc.constructor, MeterCCReport); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -165,7 +198,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 = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(cc.constructor, MeterCCReport); t.is(cc.type, 3); t.is(cc.scale, 2); @@ -187,7 +224,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 = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(cc.constructor, MeterCCReport); t.is(cc.scale, 6); }); @@ -205,7 +246,11 @@ test("the Report command (V4) should be deserialized correctly", (t) => { 0b01, // Scale2 ]), ); - const cc = new MeterCCReport(host, { nodeId: 1, data: ccData }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(cc.constructor, MeterCCReport); t.is(cc.scale, 8); }); @@ -224,7 +269,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 = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(report.constructor, MeterCCReport); // Meter type 31 (does not exist) assertZWaveError(t, () => report.persistValues(host), { @@ -246,7 +295,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 = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCReport; + t.is(report.constructor, MeterCCReport); // Meter type 4, Scale 8 (does not exist) assertZWaveError(t, () => report.persistValues(host), { @@ -275,10 +328,11 @@ test("the SupportedReport command (V2/V3) should be deserialized correctly", (t) 0b01101110, // supported scales ]), ); - const cc = new MeterCCSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCSupportedReport; + t.is(cc.constructor, MeterCCSupportedReport); t.is(cc.type, 21); t.true(cc.supportsReset); @@ -297,10 +351,11 @@ test("the SupportedReport command (V4/V5) should be deserialized correctly", (t) 1, ]), ); - const cc = new MeterCCSupportedReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as MeterCCSupportedReport; + t.is(cc.constructor, MeterCCSupportedReport); t.is(cc.type, 21); t.true(cc.supportsReset); @@ -319,7 +374,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,10 +389,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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as MeterCC; 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..bef3e1dfb1c7 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelAssociationCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, MultiChannelAssociationCCGet, MultiChannelAssociationCCRemove, MultiChannelAssociationCCReport, @@ -8,11 +9,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 +21,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 +29,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 +39,17 @@ test("the SupportedGroupingsReport command should be deserialized correctly", (t 7, // # of groups ]), ); - const cc = new MultiChannelAssociationCCSupportedGroupingsReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as MultiChannelAssociationCCSupportedGroupingsReport; + t.is(cc.constructor, MultiChannelAssociationCCSupportedGroupingsReport); 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 +64,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 +95,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 +131,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 +145,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,10 +161,11 @@ test("the Report command should be deserialized correctly (node IDs only)", (t) 5, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as MultiChannelAssociationCCReport; + t.is(cc.constructor, MultiChannelAssociationCCReport); t.is(cc.groupId, 5); t.is(cc.maxNodes, 9); @@ -190,10 +190,11 @@ test("the Report command should be deserialized correctly (endpoint addresses on 0b11010111, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as MultiChannelAssociationCCReport; + t.is(cc.constructor, MultiChannelAssociationCCReport); t.deepEqual(cc.nodeIds, []); t.deepEqual(cc.endpoints, [ @@ -227,10 +228,11 @@ test("the Report command should be deserialized correctly (both options)", (t) = 0b11010111, ]), ); - const cc = new MultiChannelAssociationCCReport(host, { - nodeId: 4, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 4 } as any, + ) as MultiChannelAssociationCCReport; + t.is(cc.constructor, MultiChannelAssociationCCReport); t.deepEqual(cc.nodeIds, [1, 5, 9]); t.deepEqual(cc.endpoints, [ @@ -246,7 +248,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 +263,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 +294,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 +330,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 +344,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 +352,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..87db658c682e 100644 --- a/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultiChannelCC.test.ts @@ -1,4 +1,4 @@ -import type { CommandClass } from "@zwave-js/cc"; +import { CommandClass } from "@zwave-js/cc"; import { BasicCCGet, BasicCCReport, @@ -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, + endpointIndex: 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,10 +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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as MultiChannelCC; t.is(cc.constructor, MultiChannelCC); }); @@ -188,10 +185,9 @@ 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, + endpointIndex: 2, }), ); t.true(ccRequest.expectsCCResponse()); @@ -199,12 +195,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, + endpointIndex: 2, }), - ) as MultiChannelCCCommandEncapsulation; + ); // A multicast request never expects a response ccRequest.destination = [1, 2, 3]; t.false(ccRequest.expectsCCResponse()); @@ -212,10 +207,9 @@ 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, + endpointIndex: 2, targetValue: 7, }), ); @@ -224,15 +218,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, + endpointIndex: 2, }), ); const ccResponse = MultiChannelCC.encapsulate( - host, - new BasicCCReport(host, { + new BasicCCReport({ nodeId: ccRequest.nodeId, currentValue: 7, }), @@ -244,17 +236,15 @@ 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, + endpointIndex: 2, }), ); const ccResponse = MultiChannelCC.encapsulate( - host, - new BasicCCGet(host, { + new BasicCCGet({ nodeId: ccRequest.nodeId, - endpoint: 2, + endpointIndex: 2, }), ); ccResponse.endpointIndex = 2; @@ -264,14 +254,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, + endpointIndex: 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..753e22153ba5 100644 --- a/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/MultilevelSwitchCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, MultilevelSwitchCC, MultilevelSwitchCCGet, MultilevelSwitchCCReport, @@ -9,11 +10,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 +23,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 +44,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 +66,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,10 +82,11 @@ test("the Report command (V1) should be deserialized correctly", (t) => { 55, // current value ]), ); - const cc = new MultilevelSwitchCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as MultilevelSwitchCCReport; + t.is(cc.constructor, MultilevelSwitchCCReport); t.is(cc.currentValue, 55); t.is(cc.targetValue, undefined); @@ -92,10 +102,11 @@ test("the Report command (v4) should be deserialized correctly", (t) => { 1, // duration ]), ); - const cc = new MultilevelSwitchCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as MultilevelSwitchCCReport; + t.is(cc.constructor, MultilevelSwitchCCReport); t.is(cc.currentValue, 55); t.is(cc.targetValue, 66); @@ -104,7 +115,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 +123,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 +142,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,17 +160,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, { - nodeId: 2, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 2 } as any, + ) as MultilevelSwitchCC; 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..54222320b630 100644 --- a/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/PowerlevelCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, Powerlevel, PowerlevelCC, PowerlevelCCGet, @@ -7,11 +8,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 +20,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 +41,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 +57,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 +73,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,10 +84,11 @@ test("the Report command should be deserialized correctly (NormalPower)", (t) => 50, // timeout (ignored because NormalPower) ]), ); - const cc = new PowerlevelCCReport(host, { - nodeId: 5, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 5 } as any, + ) as PowerlevelCCReport; + t.is(cc.constructor, PowerlevelCCReport); t.is(cc.powerlevel, Powerlevel["Normal Power"]); t.is(cc.timeout, undefined); // timeout does not apply to NormalPower @@ -103,10 +102,11 @@ test("the Report command should be deserialized correctly (custom power)", (t) = 50, // timeout (ignored because NormalPower) ]), ); - const cc = new PowerlevelCCReport(host, { - nodeId: 5, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 5 } as any, + ) as PowerlevelCCReport; + t.is(cc.constructor, PowerlevelCCReport); t.is(cc.powerlevel, Powerlevel["-3 dBm"]); t.is(cc.timeout, 50); // timeout does not apply to NormalPower @@ -116,9 +116,9 @@ 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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as PowerlevelCC; 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..d9eddc9e718b 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActivationCC.test.ts @@ -1,14 +1,12 @@ import { + CommandClass, SceneActivationCC, SceneActivationCCSet, 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 +17,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 +28,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 +44,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,10 +55,11 @@ test("the Set command should be deserialized correctly", (t) => { 0x00, // 0 seconds ]), ); - const cc = new SceneActivationCCSet(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as SceneActivationCCSet; + t.is(cc.constructor, SceneActivationCCSet); t.is(cc.sceneId, 15); t.deepEqual(cc.dimmingDuration, new Duration(0, "seconds")); @@ -70,10 +69,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, { - nodeId: 2, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 2 } as any, + ) as SceneActivationCC; 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..ed40a7b1cb21 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneActuatorConfigurationCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, SceneActuatorConfigurationCC, SceneActuatorConfigurationCCGet, SceneActuatorConfigurationCCReport, @@ -6,11 +7,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 +19,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 +29,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 +48,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 +67,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,10 +79,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x05, // dimmingDuration ]), ); - const cc = new SceneActuatorConfigurationCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as SceneActuatorConfigurationCCReport; + t.is(cc.constructor, SceneActuatorConfigurationCCReport); t.is(cc.sceneId, 55); t.is(cc.level, 0x50); @@ -95,9 +94,9 @@ 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, { - nodeId: 2, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 2 } as any, + ) as SceneActuatorConfigurationCC; 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..64966795cb65 100644 --- a/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/SceneControllerConfigurationCC.test.ts @@ -1,15 +1,13 @@ import { + CommandClass, SceneControllerConfigurationCC, SceneControllerConfigurationCCGet, SceneControllerConfigurationCCReport, 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 +18,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 +29,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 +47,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 +65,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,10 +77,11 @@ test("the Report command (v1) should be deserialized correctly", (t) => { 0x05, // dimming duration ]), ); - const cc = new SceneControllerConfigurationCCReport(host, { - nodeId: 2, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 2 } as any, + ) as SceneControllerConfigurationCCReport; + t.is(cc.constructor, SceneControllerConfigurationCCReport); t.is(cc.groupId, 3); t.is(cc.sceneId, 240); @@ -131,13 +89,12 @@ 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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as SceneControllerConfigurationCC; 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..ea0b4e98dac1 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanModeCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, ThermostatFanMode, ThermostatFanModeCCGet, ThermostatFanModeCCReport, @@ -6,11 +7,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 +19,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 +40,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,42 +55,21 @@ 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, { - nodeId: 5, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 5 } as any, + ) as ThermostatFanModeCCReport; + t.is(cc.constructor, ThermostatFanModeCCReport); t.is(cc.mode, ThermostatFanMode["Auto high"]); t.is(cc.off, true); 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..1766069a644c 100644 --- a/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/ThermostatFanStateCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, ThermostatFanState, ThermostatFanStateCC, ThermostatFanStateCCGet, @@ -6,11 +7,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 +19,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,10 +35,11 @@ test("the Report command (v1 - v2) should be deserialized correctly", (t) => { ThermostatFanState["Idle / off"], // state ]), ); - const cc = new ThermostatFanStateCCReport(host, { - nodeId: 1, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 1 } as any, + ) as ThermostatFanStateCCReport; + t.is(cc.constructor, ThermostatFanStateCCReport); t.is(cc.state, ThermostatFanState["Idle / off"]); }); @@ -49,10 +48,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, { - nodeId: 1, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 1 } as any, + ) as ThermostatFanStateCC; 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..d9f24ae44afa 100644 --- a/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/TimeCC.test.ts @@ -1,4 +1,5 @@ import { + CommandClass, TimeCC, TimeCCDateGet, TimeCCDateReport, @@ -7,11 +8,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 +20,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,10 +38,11 @@ test("the TimeReport command should be deserialized correctly", (t) => { 59, ]), ); - const cc = new TimeCCTimeReport(host, { - nodeId: 8, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 8 } as any, + ) as TimeCCTimeReport; + t.is(cc.constructor, TimeCCTimeReport); t.is(cc.hour, 14); t.is(cc.minute, 23); @@ -51,13 +50,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,10 +69,11 @@ test("the DateReport command should be deserialized correctly", (t) => { 17, ]), ); - const cc = new TimeCCDateReport(host, { - nodeId: 8, - data: ccData, - }); + const cc = CommandClass.parse( + ccData, + { sourceNodeId: 8 } as any, + ) as TimeCCDateReport; + t.is(cc.constructor, TimeCCDateReport); t.is(cc.year, 1989); t.is(cc.month, 10); @@ -84,10 +84,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, { - nodeId: 8, - data: serializedCC, - }); + const cc = CommandClass.parse( + serializedCC, + { sourceNodeId: 8 } as any, + ) as TimeCC; 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..ddb6d59d538c 100644 --- a/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts +++ b/packages/zwave-js/src/lib/test/cc/WakeUpCC.test.ts @@ -4,26 +4,22 @@ 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, + endpointIndex: 2, }); t.false(cc.expectsCCResponse()); }); test("MultiChannelCC/WakeUpCCNoMoreInformation should expect NO response", (t) => { const ccRequest = MultiChannelCC.encapsulate( - host, - new WakeUpCCNoMoreInformation(host, { + new WakeUpCCNoMoreInformation({ nodeId: 2, - endpoint: 2, + endpointIndex: 2, }), ); t.false(ccRequest.expectsCCResponse()); @@ -42,13 +38,11 @@ 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, + endpointIndex: 2, }), ); t.false(ccRequest.expectsCCResponse()); 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..29684b2a7741 100644 --- a/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts +++ b/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts @@ -1,6 +1,15 @@ import { WakeUpTime, ZWaveProtocolCCAssignSUCReturnRoute } from "@zwave-js/cc"; import { TransmitStatus, ZWaveDataRate } from "@zwave-js/core"; import { FunctionType } from "@zwave-js/serial"; +import { + AssignSUCReturnRouteRequest, + AssignSUCReturnRouteResponse, +} from "@zwave-js/serial/serialapi"; +import { + DeleteSUCReturnRouteRequest, + DeleteSUCReturnRouteRequestTransmitReport, + DeleteSUCReturnRouteResponse, +} from "@zwave-js/serial/serialapi"; import { type MockControllerBehavior, createMockZWaveRequestFrame, @@ -10,15 +19,6 @@ import { MockControllerCommunicationState, MockControllerStateKeys, } from "../../controller/MockControllerState"; -import { - AssignSUCReturnRouteRequest, - AssignSUCReturnRouteResponse, -} from "../../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; -import { - DeleteSUCReturnRouteRequest, - DeleteSUCReturnRouteRequestTransmitReport, - DeleteSUCReturnRouteResponse, -} from "../../serialapi/network-mgmt/DeleteSUCReturnRouteMessages"; import { integrationTest } from "../integrationTestSuite"; // Repro for https://github.com/zwave-js/node-zwave-js/issues/6363 @@ -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..3497bc264da4 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,40 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -79,8 +99,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,11 +108,11 @@ 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, encapsulated: innerCC, }); @@ -129,12 +149,12 @@ 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, encapsulated: innerCC, }); 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..3c9732f9c809 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,8 +63,9 @@ integrationTest( SecurityClass.S2_Unauthenticated, driver.options.securityKeys!.S2_Unauthenticated!, ); - controller.host.securityManager2 = smCtrlr; - controller.host.getHighestSecurityClass = () => + controller.securityManagers.securityManager2 = smCtrlr; + // controller.parsingContext.getHighestSecurityClass = + controller.encodingContext.getHighestSecurityClass = () => SecurityClass.S2_Unauthenticated; // Respond to Nonce Get @@ -72,10 +73,10 @@ integrationTest( 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, SOS: true, MOS: false, receiverEI: nonce, @@ -97,10 +98,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -123,11 +124,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 +144,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..f82c2d4da011 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,20 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -148,10 +149,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -173,12 +174,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 +194,8 @@ integrationTest( .map(([ccId]) => ccId) : [], }), + self.id, + self.securityManagers, ); return { action: "sendCC", cc }; } @@ -208,13 +212,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 +237,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 +265,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 +280,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..d1f0be6db1f1 100644 --- a/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts +++ b/packages/zwave-js/src/lib/test/driver/assemblePartialCCs.test.ts @@ -1,15 +1,16 @@ -import { AssociationCCReport } from "@zwave-js/cc/AssociationCC"; +import { CommandClass } from "@zwave-js/cc"; +import { type AssociationCCReport } from "@zwave-js/cc/AssociationCC"; import { BasicCCSet } from "@zwave-js/cc/BasicCC"; import { MultiCommandCCCommandEncapsulation } from "@zwave-js/cc/MultiCommandCC"; import { SecurityCCCommandEncapsulation } from "@zwave-js/cc/SecurityCC"; import { AssociationCommand } from "@zwave-js/cc/safe"; import { CommandClasses, ZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; +import { ApplicationCommandRequest } from "@zwave-js/serial/serialapi"; import { MockController, MockNode } from "@zwave-js/testing"; import ava, { type TestFn } from "ava"; import { createDefaultMockControllerBehaviors } from "../../../Utils"; import type { Driver } from "../../driver/Driver"; import { createAndStartTestingDriver } from "../../driver/DriverMock"; -import { ApplicationCommandRequest } from "../../serialapi/application/ApplicationCommandRequest"; interface TestContext { driver: Driver; @@ -50,8 +51,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,9 +62,8 @@ 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, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -73,8 +73,9 @@ test.serial( 2, 3, ]), - }); - const msg = new ApplicationCommandRequest(driver, { + { sourceNodeId: 2 } as any, + ); + const msg = new ApplicationCommandRequest({ command: cc, }); t.true(driver["assemblePartialCCs"](msg)); @@ -85,9 +86,8 @@ test.serial( "returns false when a partial CC is received that expects more reports", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -97,8 +97,9 @@ test.serial( 2, 3, ]), - }); - const msg = new ApplicationCommandRequest(driver, { + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -109,9 +110,8 @@ 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, { - nodeId: 2, - data: Buffer.from([ + const cc1 = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -121,10 +121,10 @@ test.serial( 2, 3, ]), - }); - const cc2 = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; + const cc2 = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -134,13 +134,14 @@ test.serial( 5, 6, ]), - }); - const msg1 = new ApplicationCommandRequest(driver, { + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; + 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 +155,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 +169,14 @@ 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, 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,7 +184,7 @@ 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, encapsulated: {} as any, }); @@ -198,7 +199,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,7 +207,7 @@ 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, encapsulated: {} as any, }); @@ -221,7 +222,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,9 +232,8 @@ test.serial( "returns false when a partial CC throws Deserialization_NotImplemented during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -243,14 +243,15 @@ test.serial( 2, 3, ]), - }); + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; cc.mergePartialCCs = () => { throw new ZWaveError( "not implemented", ZWaveErrorCodes.Deserialization_NotImplemented, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -261,9 +262,8 @@ test.serial( "returns false when a partial CC throws CC_NotImplemented during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -273,14 +273,15 @@ test.serial( 2, 3, ]), - }); + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; cc.mergePartialCCs = () => { throw new ZWaveError( "not implemented", ZWaveErrorCodes.CC_NotImplemented, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -291,9 +292,8 @@ test.serial( "returns false when a partial CC throws PacketFormat_InvalidPayload during merging", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -303,14 +303,15 @@ test.serial( 2, 3, ]), - }); + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; cc.mergePartialCCs = () => { throw new ZWaveError( "not implemented", ZWaveErrorCodes.PacketFormat_InvalidPayload, ); }; - const msg = new ApplicationCommandRequest(driver, { + const msg = new ApplicationCommandRequest({ command: cc, }); t.false(driver["assemblePartialCCs"](msg)); @@ -319,9 +320,8 @@ test.serial( test.serial("passes other errors during merging through", (t) => { const { driver } = t.context; - const cc = new AssociationCCReport(driver, { - nodeId: 2, - data: Buffer.from([ + const cc = CommandClass.parse( + Buffer.from([ CommandClasses.Association, AssociationCommand.Report, 1, @@ -331,11 +331,12 @@ test.serial("passes other errors during merging through", (t) => { 2, 3, ]), - }); + { sourceNodeId: 2 } as any, + ) as AssociationCCReport; 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/bootloaderDetection.test.ts b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts index 0f0e02934987..3d36d73752c3 100644 --- a/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts +++ b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts @@ -14,7 +14,7 @@ integrationTest( async customSetup(driver, mockController, mockNode) { const sendBootloaderMessageInChunks: MockControllerBehavior = { - async onHostData(host, self, ctrl) { + async onHostData(self, ctrl) { // if ( // ctrl.length === 1 // && (ctrl[0] === MessageHeaders.NAK || ctrl[0] === 0x32) 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..7591f70e8848 100644 --- a/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts +++ b/packages/zwave-js/src/lib/test/driver/computeNetCCPayloadSize.test.ts @@ -2,12 +2,12 @@ import { FirmwareUpdateMetaDataCC } from "@zwave-js/cc/FirmwareUpdateMetaDataCC" import { MultiChannelCCCommandEncapsulation } from "@zwave-js/cc/MultiChannelCC"; import { SecurityCCCommandEncapsulation } from "@zwave-js/cc/SecurityCC"; import { EncapsulationFlags, TransmitOptions } from "@zwave-js/core"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; import { MockController } from "@zwave-js/testing"; import ava, { type TestFn } from "ava"; import { createDefaultMockControllerBehaviors } from "../../../Utils"; import type { Driver } from "../../driver/Driver"; import { createAndStartTestingDriver } from "../../driver/DriverMock"; -import { SendDataRequest } from "../../serialapi/transport/SendDataMessages"; interface TestContext { driver: Driver; @@ -43,23 +43,23 @@ 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, encapsulated: {} as any, }), transmitOptions: TransmitOptions.DEFAULT, }); - testMsg1.command.encapsulated = undefined as any; + 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, encapsulated: multiChannelCC, }), @@ -68,7 +68,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..5a7023b04c3f 100644 --- a/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts +++ b/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts @@ -7,6 +7,13 @@ import { getZWaveChipType, } from "@zwave-js/core"; import { FunctionType } from "@zwave-js/serial"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; +import { + SendDataAbort, + SendDataRequest, + SendDataRequestTransmitReport, + SendDataResponse, +} from "@zwave-js/serial/serialapi"; import { type MockControllerBehavior } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import sinon from "sinon"; @@ -14,13 +21,6 @@ import { MockControllerCommunicationState, MockControllerStateKeys, } from "../../controller/MockControllerState"; -import { SoftResetRequest } from "../../serialapi/misc/SoftResetRequest"; -import { - SendDataAbort, - SendDataRequest, - SendDataRequestTransmitReport, - SendDataResponse, -} from "../../serialapi/transport/SendDataMessages"; import { integrationTest } from "../integrationTestSuite"; import { integrationTest as integrationTestMulti } from "../integrationTestSuiteMulti"; @@ -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..aaab6a3edd2e 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,20 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -99,10 +100,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -125,17 +126,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 +154,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 +196,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 +231,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 +254,20 @@ 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: [], + reportsToFollow: 0, + }); + const cc = SecurityCC.encapsulate( + self.id, + self.securityManagers.securityManager!, + response, + ); cc.nonce = receiverNonce; await self.sendToController( createMockZWaveRequestFrame(cc, { @@ -288,8 +292,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 +315,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, { @@ -339,12 +344,15 @@ integrationTest( const parseS0CC: MockNodeBehavior = { handleCC(controller, self, receivedCC) { // We don't support sequenced commands here - if ( - receivedCC instanceof SecurityCCCommandEncapsulation - ) { - receivedCC.mergePartialCCs(undefined as any, []); + if (receivedCC instanceof SecurityCCCommandEncapsulation) { + receivedCC.mergePartialCCs([], { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...self.encodingContext, + ...self.securityManagers, + }); } - + // This just decodes - we need to call further handlers 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..3cb19c10bc05 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 }; @@ -63,11 +63,13 @@ integrationTest( const parseS0CC: MockNodeBehavior = { handleCC(controller, self, receivedCC) { // We don't support sequenced commands here - if ( - receivedCC - instanceof SecurityCCCommandEncapsulation - ) { - receivedCC.mergePartialCCs(undefined as any, []); + if (receivedCC instanceof SecurityCCCommandEncapsulation) { + receivedCC.mergePartialCCs([], { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...self.encodingContext, + ...self.securityManagers, + }); } // This just decodes - we need to call further handlers return undefined; @@ -81,8 +83,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 +121,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..d92785fac513 100644 --- a/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts +++ b/packages/zwave-js/src/lib/test/driver/nodeAsleepNoReject.test.ts @@ -1,9 +1,9 @@ import { BasicCCGet, BasicCCSet } from "@zwave-js/cc"; import { MessagePriority, NodeStatus } from "@zwave-js/core"; +import { type SendDataRequest } from "@zwave-js/serial/serialapi"; import { MOCK_FRAME_ACK_TIMEOUT, MockZWaveFrameType } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import path from "node:path"; -import { type SendDataRequest } from "../../serialapi/transport/SendDataMessages"; import { integrationTest } from "../integrationTestSuite"; // Repro from #1078 @@ -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..43e3d0d066b8 100644 --- a/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts +++ b/packages/zwave-js/src/lib/test/driver/receiveMessages.test.ts @@ -1,9 +1,9 @@ import { WakeUpCCIntervalSet } from "@zwave-js/cc/WakeUpCC"; +import { ApplicationCommandRequest } from "@zwave-js/serial/serialapi"; import { MockController } from "@zwave-js/testing"; import ava, { type TestFn } from "ava"; import type { Driver } from "../../driver/Driver"; import { createAndStartTestingDriver } from "../../driver/DriverMock"; -import { ApplicationCommandRequest } from "../../serialapi/application/ApplicationCommandRequest"; interface TestContext { driver: Driver; @@ -36,14 +36,16 @@ 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["getEncodingContext"]()), + ); 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..f255ee318a9d 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,12 @@ 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([], { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...self.encodingContext, + ...self.securityManagers, + }); } return undefined; }, @@ -121,10 +127,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -146,10 +152,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -168,13 +174,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..f378109ff57b 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,17 @@ 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: [], + reportsToFollow: 0, + }); + 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 +121,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 +142,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 +170,12 @@ 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([], { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...self.encodingContext, + ...self.securityManagers, + }); } // 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..4d18d128968c 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,15 @@ 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: [], + reportsToFollow: 0, + }); const cc = SecurityCC.encapsulate( - self.host, + self.id, + self.securityManagers.securityManager!, response, ); cc.nonce = receiverNonce; @@ -154,8 +153,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 +177,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 +207,12 @@ integrationTest( if ( receivedCC instanceof SecurityCCCommandEncapsulation ) { - receivedCC.mergePartialCCs(undefined as any, []); + receivedCC.mergePartialCCs([], { + sourceNodeId: controller.ownNodeId, + __internalIsMockNode: true, + ...self.encodingContext, + ...self.securityManagers, + }); } // This just decodes - we need to call further handlers return undefined; @@ -229,8 +234,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..623664cb10a3 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,20 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -109,10 +110,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -133,16 +134,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 +164,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 +224,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 +243,20 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -274,10 +278,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -302,11 +306,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 +360,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 +379,19 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -408,10 +413,10 @@ 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, SOS: true, MOS: false, receiverEI: nonce, @@ -432,16 +437,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 +471,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 +512,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/secureAndSupervisionEncap.test.ts b/packages/zwave-js/src/lib/test/driver/secureAndSupervisionEncap.test.ts index e3958984527c..889d99bcefad 100644 --- a/packages/zwave-js/src/lib/test/driver/secureAndSupervisionEncap.test.ts +++ b/packages/zwave-js/src/lib/test/driver/secureAndSupervisionEncap.test.ts @@ -1,7 +1,7 @@ import { SecurityCCNonceGet } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; import path from "node:path"; -import { SendDataRequest } from "../../serialapi/transport/SendDataMessages"; import { integrationTest } from "../integrationTestSuite"; integrationTest( 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..a9206ca96abd 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataAbortAfterTimeout.test.ts @@ -6,14 +6,14 @@ import { } from "../../controller/MockControllerState"; import { TransmitStatus } from "@zwave-js/core"; -import { SoftResetRequest } from "../../serialapi/misc/SoftResetRequest"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; import { SendDataAbort, SendDataRequest, SendDataRequestTransmitReport, SendDataResponse, -} from "../../serialapi/transport/SendDataMessages"; +} from "@zwave-js/serial/serialapi"; import { integrationTest } from "../integrationTestSuite"; let shouldTimeOut: boolean; @@ -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..53e9d33900c7 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts @@ -13,23 +13,23 @@ import { ZWaveErrorCodes, assertZWaveError, } from "@zwave-js/core"; -import path from "node:path"; -import Sinon from "sinon"; -import { determineNIF } from "../../controller/NodeInformationFrame"; import { SerialAPIStartedRequest, SerialAPIWakeUpReason, -} from "../../serialapi/application/SerialAPIStartedRequest"; -import { SoftResetRequest } from "../../serialapi/misc/SoftResetRequest"; +} from "@zwave-js/serial/serialapi"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; import { RequestNodeInfoRequest, RequestNodeInfoResponse, -} from "../../serialapi/network-mgmt/RequestNodeInfoMessages"; +} from "@zwave-js/serial/serialapi"; import { SendDataAbort, SendDataRequest, SendDataResponse, -} from "../../serialapi/transport/SendDataMessages"; +} from "@zwave-js/serial/serialapi"; +import path from "node:path"; +import Sinon from "sinon"; +import { determineNIF } from "../../controller/NodeInformationFrame"; import { integrationTest } from "../integrationTestSuite"; import { integrationTest as integrationTestMulti } from "../integrationTestSuiteMulti"; @@ -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..a54177c9a97d 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataMissingResponse.test.ts @@ -17,7 +17,7 @@ import { SendDataAbort, SendDataRequest, SendDataRequestTransmitReport, -} from "../../serialapi/transport/SendDataMessages"; +} from "@zwave-js/serial/serialapi"; import { integrationTest } from "../integrationTestSuite"; let shouldTimeOut: boolean; @@ -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..a3b3009c460a 100644 --- a/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts +++ b/packages/zwave-js/src/lib/test/driver/unresponsiveStick.test.ts @@ -1,13 +1,13 @@ import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; import { FunctionType } from "@zwave-js/serial"; -import { type MockControllerBehavior } from "@zwave-js/testing"; -import { wait } from "alcalzone-shared/async"; -import Sinon from "sinon"; import { GetControllerIdRequest, type GetControllerIdResponse, -} from "../../serialapi/memory/GetControllerIdMessages"; -import { SoftResetRequest } from "../../serialapi/misc/SoftResetRequest"; +} from "@zwave-js/serial/serialapi"; +import { SoftResetRequest } from "@zwave-js/serial/serialapi"; +import { type MockControllerBehavior } from "@zwave-js/testing"; +import { wait } from "alcalzone-shared/async"; +import Sinon from "sinon"; import { integrationTest } from "../integrationTestSuite"; let shouldRespond = true; @@ -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) { @@ -46,11 +46,11 @@ integrationTest( mockController.autoAckHostMessages = false; const ids = await driver.sendMessage( - new GetControllerIdRequest(driver), + new GetControllerIdRequest(), { 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; @@ -97,7 +97,7 @@ integrationTest( t, () => driver.sendMessage( - new GetControllerIdRequest(driver), + new GetControllerIdRequest(), { supportCheck: 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; } @@ -164,7 +164,7 @@ integrationTest( t, () => driver.sendMessage( - new GetControllerIdRequest(driver), + new GetControllerIdRequest(), { supportCheck: false }, ), { 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..ac993ddadc22 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, @@ -26,11 +28,10 @@ import { messageTypes, priority, } from "@zwave-js/serial"; +import { SendDataRequest } from "@zwave-js/serial/serialapi"; 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"; const MockRequestMessageWithExpectation_FunctionType = 0xfa as unknown as FunctionType; @@ -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,47 +249,33 @@ export function createTestNode( return !ret.isListening && !ret.isFrequentListening; }, - status: options.status - ?? (options.isListening ? NodeStatus.Alive : NodeStatus.Asleep), - interviewStage: options.interviewStage ?? InterviewStage.Complete, - setEndpoint: (endpoint) => { endpointCache.set( endpoint.index, 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,16 +288,6 @@ 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; - }, - // These are copied from Node.ts getHighestSecurityClass(): SecurityClass | undefined { if (securityClasses.size === 0) return undefined; @@ -342,10 +318,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 +339,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..d21207e9b0cf 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 @@ -1,6 +1,6 @@ -import { BinarySwitchCommand, EntryControlCommand } from "@zwave-js/cc"; +import { CommandClass, EntryControlCommand } from "@zwave-js/cc"; import { BinarySwitchCCReport } from "@zwave-js/cc/BinarySwitchCC"; -import { EntryControlCCNotification } from "@zwave-js/cc/EntryControlCC"; +import { type EntryControlCCNotification } from "@zwave-js/cc/EntryControlCC"; import { type CommandClassInfo, CommandClasses } from "@zwave-js/core"; import test from "ava"; import sinon from "sinon"; @@ -64,17 +64,10 @@ 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, + currentValue: true, + }); await node.handleCommand(command); t.true( @@ -119,13 +112,10 @@ test.serial( Buffer.alloc(12, 0xff), ]); - const command = new EntryControlCCNotification( - fakeDriver as unknown as Driver, - { - nodeId: node.id, - data: buf, - }, - ); + const command = CommandClass.parse( + buf, + { sourceNodeId: node.id } as any, + ) as EntryControlCCNotification; 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..623f835c33e3 100644 --- a/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts +++ b/packages/zwave-js/src/lib/test/node/legacyRefreshActuatorSensorCCs.test.ts @@ -4,9 +4,9 @@ import { MultilevelSwitchCCSet, } from "@zwave-js/cc"; import { CommandClasses } from "@zwave-js/core"; +import { ApplicationUpdateRequestNodeInfoReceived } from "@zwave-js/serial/serialapi"; import { type MockNodeBehavior, MockZWaveFrameType } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; -import { ApplicationUpdateRequestNodeInfoReceived } from "../../serialapi/application/ApplicationUpdateRequest"; import { integrationTest } from "../integrationTestSuite"; integrationTest( @@ -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/MPDU.ts b/packages/zwave-js/src/lib/zniffer/MPDU.ts index 29f36d7c07d6..55e81b0c8bd2 100644 --- a/packages/zwave-js/src/lib/zniffer/MPDU.ts +++ b/packages/zwave-js/src/lib/zniffer/MPDU.ts @@ -22,6 +22,7 @@ import { type ZnifferFrameInfo, ZnifferFrameType, } from "@zwave-js/serial"; +import { parseRSSI } from "@zwave-js/serial/serialapi"; import { type AllOrNone, buffer2hex, @@ -29,7 +30,6 @@ import { staticExtends, } from "@zwave-js/shared"; import { padStart } from "alcalzone-shared/strings"; -import { parseRSSI } from "../serialapi/transport/SendDataShared"; import { ExplorerFrameCommand, LongRangeFrameType, diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 0447d8b11587..b28d0fc33a7e 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -5,15 +5,21 @@ import { Security2CCNonceReport, SecurityCCNonceReport, } from "@zwave-js/cc"; +import { DeviceConfig } from "@zwave-js/config"; import { CommandClasses, + type FrameType, type LogConfig, MPDUHeaderType, + type MaybeNotKnown, + NODE_ID_BROADCAST, + NODE_ID_BROADCAST_LR, type RSSI, SPANState, SecurityClass, SecurityManager, SecurityManager2, + type SecurityManagers, type UnknownZWaveChipType, ZWaveError, ZWaveErrorCodes, @@ -24,6 +30,8 @@ import { isLongRangeNodeId, securityClassIsS2, } from "@zwave-js/core"; +import { sdkVersionGte } from "@zwave-js/core"; +import { type CCParsingContext, type HostIDs } from "@zwave-js/host"; import { type ZWaveSerialPortImplementation, type ZnifferDataMessage, @@ -62,10 +70,8 @@ import { createDeferredPromise, } from "alcalzone-shared/deferred-promise"; 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 +208,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" | "frameType" | keyof SecurityManagers + >; private _destroyPromise: DeferredPromise | undefined; private get wasDestroyed(): boolean { @@ -506,20 +568,27 @@ supported frequencies: ${ } // TODO: Support parsing multicast S2 frames - - const ctx = new ZnifferCCParsingContext( - destNodeId, - mpdu.homeId, - destSecurityManager, - destSecurityManager2, - destSecurityManagerLR, - ); + const frameType: FrameType = + mpdu.headerType === MPDUHeaderType.Multicast + ? "multicast" + : (destNodeId === NODE_ID_BROADCAST + || destNodeId === NODE_ID_BROADCAST_LR) + ? "broadcast" + : "singlecast"; try { - cc = CommandClass.from(ctx, { - data: mpdu.payload, - fromEncapsulation: false, - nodeId: mpdu.sourceNodeId, - }); + cc = CommandClass.parse( + mpdu.payload, + { + homeId: mpdu.homeId, + ownNodeId: destNodeId, + sourceNodeId: mpdu.sourceNodeId, + frameType, + securityManager: destSecurityManager, + securityManager2: destSecurityManager2, + securityManagerLR: destSecurityManagerLR, + ...this.parsingContext, + }, + ); } catch (e: any) { // Ignore console.error(e.stack); diff --git a/packages/zwave-js/zwave-js.api.md b/packages/zwave-js/zwave-js.api.md index 38873268dfd7..3b92f73a66af 100644 --- a/packages/zwave-js/zwave-js.api.md +++ b/packages/zwave-js/zwave-js.api.md @@ -14,8 +14,10 @@ import { BeamingInfo } from '@zwave-js/core/safe'; import { BeamingInfo as BeamingInfo_2 } from '@zwave-js/core'; import { BootloaderChunk } from '@zwave-js/serial'; import { buffer2hex } from '@zwave-js/shared/safe'; +import { CCAPIHost } from '@zwave-js/cc'; import { CCAPIs } from '@zwave-js/cc'; import { CCConstructor } from '@zwave-js/cc'; +import { CCId } from '@zwave-js/core'; import { CCNameOrId } from '@zwave-js/cc'; import { CommandClass } from '@zwave-js/cc'; import { CommandClasses } from '@zwave-js/core/safe'; @@ -27,13 +29,13 @@ import { CompareResult } from 'alcalzone-shared/comparable'; import { ConfigManager } from '@zwave-js/config'; import { ControllerCapabilities } from '@zwave-js/core'; import { ControllerLogContext } from '@zwave-js/core'; -import { ControllerLogger } from '@zwave-js/core'; import { ControllerNodeLogContext } from '@zwave-js/core'; import { ControllerRole } from '@zwave-js/core'; import { ControllerSelfLogContext } from '@zwave-js/core'; import { ControllerStatus } from '@zwave-js/core/safe'; import { ControllerStatus as ControllerStatus_2 } from '@zwave-js/core'; import { ControllerValueLogContext } from '@zwave-js/core'; +import type { ControlsCC } from '@zwave-js/core'; import { DataDirection } from '@zwave-js/core'; import { DataRate } from '@zwave-js/core/safe'; import { DataRate as DataRate_2 } from '@zwave-js/core'; @@ -44,8 +46,10 @@ import { DeviceID } from '@zwave-js/config'; import { Duration } from '@zwave-js/core/safe'; import { Duration as Duration_2 } from '@zwave-js/core'; import { DurationUnit } from '@zwave-js/core/safe'; +import type { EndpointId } from '@zwave-js/core'; import { EntryControlDataTypes } from '@zwave-js/cc/safe'; import { EntryControlEventTypes } from '@zwave-js/cc/safe'; +import { EventHandler } from '@zwave-js/shared'; import { Expand } from '@zwave-js/shared'; import { Expand as Expand_2 } from '@zwave-js/shared/safe'; import { extractFirmware } from '@zwave-js/core'; @@ -54,6 +58,8 @@ import type { FileSystem as FileSystem_2 } from '@zwave-js/host'; import { Firmware } from '@zwave-js/core'; import { FirmwareFileFormat } from '@zwave-js/core'; import { FirmwareUpdateCapabilities } from '@zwave-js/cc'; +import { FirmwareUpdateMetaDataCCGet } from '@zwave-js/cc'; +import { FirmwareUpdateMetaDataCCMetaDataGet } from '@zwave-js/cc'; import { FirmwareUpdateOptions } from '@zwave-js/cc'; import type { FirmwareUpdateProgress } from '@zwave-js/cc/safe'; import type { FirmwareUpdateResult } from '@zwave-js/cc/safe'; @@ -62,50 +68,59 @@ import { FirmwareUpdateStatus } from '@zwave-js/cc/safe'; import { FLiRS } from '@zwave-js/core/safe'; import { FLiRS as FLiRS_2 } from '@zwave-js/core'; import { formatId } from '@zwave-js/shared/safe'; +import { FrameType } from '@zwave-js/core'; import { FunctionType } from '@zwave-js/serial'; import { GenericDeviceClass } from '@zwave-js/core/safe'; +import { GetAllEndpoints } from '@zwave-js/core'; +import type { GetCCs } from '@zwave-js/core'; +import { GetEndpoint } from '@zwave-js/core'; import { getEnumMemberName } from '@zwave-js/shared/safe'; import { GraphNode } from '@zwave-js/core'; import { guessFirmwareFileFormat } from '@zwave-js/core'; -import { ICommandClass } from '@zwave-js/core'; -import { ICommandClassContainer } from '@zwave-js/cc'; +import { Interpreter } from 'xstate'; +import { InterpreterFrom } from 'xstate'; +import { InterviewContext } from '@zwave-js/cc'; import { InterviewStage } from '@zwave-js/core/safe'; +import { InterviewStage as InterviewStage_2 } from '@zwave-js/core'; +import type { IsCCSecure } from '@zwave-js/core'; import { isRssiError } from '@zwave-js/core/safe'; -import { IVirtualEndpoint } from '@zwave-js/core/safe'; -import { IVirtualNode } from '@zwave-js/core'; -import type { IZWaveEndpoint } from '@zwave-js/core'; -import { IZWaveNode } from '@zwave-js/core'; import { JSONObject } from '@zwave-js/shared/safe'; import { JSONObject as JSONObject_2 } from '@zwave-js/shared'; import { KEXFailType } from '@zwave-js/cc'; import { LogConfig } from '@zwave-js/core'; import { LogContext } from '@zwave-js/core'; +import { LogNodeOptions } from '@zwave-js/core'; import { LongRangeChannel } from '@zwave-js/core'; import { MaybeNotKnown } from '@zwave-js/core'; import { MaybeUnknown } from '@zwave-js/core'; import { Message } from '@zwave-js/serial'; import { MessageBaseOptions } from '@zwave-js/serial'; -import { MessageDeserializationOptions } from '@zwave-js/serial'; +import { MessageEncodingContext } from '@zwave-js/serial'; import { MessageHeaders } from '@zwave-js/serial'; import { MessageOptions } from '@zwave-js/serial'; import { MessageOrCCLogEntry } from '@zwave-js/core'; +import { MessageParsingContext } from '@zwave-js/serial'; import { MessagePriority } from '@zwave-js/core'; +import { MessageRaw } from '@zwave-js/serial'; import { MessageType } from '@zwave-js/serial'; import type { MetadataUpdatedArgs } from '@zwave-js/core/safe'; import { MockControllerBehavior } from '@zwave-js/testing'; import { MockNodeBehavior } from '@zwave-js/testing'; +import type { ModifyCCs } from '@zwave-js/core'; import { MPDUHeaderType } from '@zwave-js/core/safe'; import { MPDUHeaderType as MPDUHeaderType_2 } from '@zwave-js/core'; import { MulticastCC } from '@zwave-js/core'; -import { MulticastDestination } from '@zwave-js/core/safe'; +import { MulticastDestination } from '@zwave-js/core'; +import { MulticastDestination as MulticastDestination_2 } from '@zwave-js/core/safe'; import { MultilevelSwitchCommand } from '@zwave-js/cc/safe'; import { NODE_ID_BROADCAST } from '@zwave-js/core/safe'; import { NODE_ID_BROADCAST as NODE_ID_BROADCAST_2 } from '@zwave-js/core'; import { NODE_ID_BROADCAST_LR } from '@zwave-js/core/safe'; import { NODE_ID_BROADCAST_LR as NODE_ID_BROADCAST_LR_2 } from '@zwave-js/core'; import { NODE_ID_MAX } from '@zwave-js/core/safe'; +import { NodeId } from '@zwave-js/core/safe'; import { NodeIDType } from '@zwave-js/core'; -import type { NodeSchedulePollOptions } from '@zwave-js/host'; +import { NodeSchedulePollOptions } from '@zwave-js/host'; import { NodeStatus } from '@zwave-js/core/safe'; import { NodeType } from '@zwave-js/core/safe'; import { NodeType as NodeType_2 } from '@zwave-js/core'; @@ -114,6 +129,7 @@ import type { NotificationCCReport } from '@zwave-js/cc/NotificationCC'; import { num2hex } from '@zwave-js/shared/safe'; import { NVMAdapter } from '@zwave-js/nvmedit'; import { parseQRCodeString } from '@zwave-js/core'; +import { PersistValuesContext } from '@zwave-js/cc'; import { Powerlevel } from '@zwave-js/cc/safe'; import { Powerlevel as Powerlevel_2 } from '@zwave-js/cc'; import { PowerlevelTestStatus } from '@zwave-js/cc/safe'; @@ -127,8 +143,10 @@ import { ProtocolVersion } from '@zwave-js/core/safe'; import { ProtocolVersion as ProtocolVersion_2 } from '@zwave-js/core'; import { QRCodeVersion } from '@zwave-js/core'; import { QRProvisioningInformation } from '@zwave-js/core'; +import { QuerySecurityClasses } from '@zwave-js/core'; import { ReadonlyObjectKeyMap } from '@zwave-js/shared'; import { ReadonlyThrowingMap } from '@zwave-js/shared'; +import { RefreshValuesContext } from '@zwave-js/cc'; import { ResponsePredicate } from '@zwave-js/serial'; import { ResponseRole } from '@zwave-js/serial'; import { RFRegion } from '@zwave-js/core/safe'; @@ -144,7 +162,6 @@ import { rssiToString } from '@zwave-js/core'; import { Scale } from '@zwave-js/core/safe'; import type { SecurityClass } from '@zwave-js/core/safe'; import { SecurityClass as SecurityClass_2 } from '@zwave-js/core'; -import { SecurityClassOwner } from '@zwave-js/core'; import { SecurityManager } from '@zwave-js/core'; import { SecurityManager2 } from '@zwave-js/core'; import { SendCommandOptions } from '@zwave-js/core'; @@ -155,10 +172,14 @@ import { SerialApiInitData } from '@zwave-js/core'; import type { SerializedValue } from '@zwave-js/core/safe'; import type { SerialPort } from 'serialport'; import { SetbackState } from '@zwave-js/cc'; +import { SetSecurityClass } from '@zwave-js/core'; import { SetValueAPIOptions } from '@zwave-js/cc'; import { SetValueResult } from '@zwave-js/cc/safe'; import { SinglecastCC } from '@zwave-js/core'; import { SpecificDeviceClass } from '@zwave-js/core/safe'; +import { StateMachine } from 'xstate'; +import type { SupportsCC } from '@zwave-js/core'; +import { SupportsCC as SupportsCC_2 } from '@zwave-js/core/safe'; import { Switchpoint } from '@zwave-js/cc'; import { TransactionProgress } from '@zwave-js/core'; import { TransactionProgressListener } from '@zwave-js/core'; @@ -184,18 +205,17 @@ import type { ValueNotificationArgs } from '@zwave-js/core/safe'; import type { ValueRemovedArgs } from '@zwave-js/core/safe'; import { ValueType } from '@zwave-js/core/safe'; import type { ValueUpdatedArgs } from '@zwave-js/core/safe'; +import { VirtualEndpointId } from '@zwave-js/core/safe'; import type { Weekday } from '@zwave-js/cc/safe'; import { ZnifferDataMessage } from '@zwave-js/serial'; import { ZnifferFrameInfo } from '@zwave-js/serial'; import { ZnifferProtocolDataRate } from '@zwave-js/core'; import { ZnifferRegion } from '@zwave-js/core'; import { ZWaveApiVersion } from '@zwave-js/core/safe'; -import type { ZWaveApplicationHost } from '@zwave-js/host'; import { ZWaveDataRate } from '@zwave-js/core'; import { ZWaveError } from '@zwave-js/core/safe'; import { ZWaveError as ZWaveError_2 } from '@zwave-js/core'; import { ZWaveErrorCodes } from '@zwave-js/core/safe'; -import type { ZWaveHost } from '@zwave-js/host'; import type { ZWaveHostOptions } from '@zwave-js/host'; import { ZWaveLibraryTypes } from '@zwave-js/core/safe'; import { ZWavePlusNodeType } from '@zwave-js/cc'; @@ -239,6 +259,39 @@ export { BeamingInfo } export { buffer2hex } +// Warning: (ae-forgotten-export) The symbol "ApplicationCommandRequest" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "BridgeApplicationCommandRequest" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "CommandRequest" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CommandRequest = ApplicationCommandRequest | BridgeApplicationCommandRequest; + +// Warning: (ae-missing-release-tag) "ContainsCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ContainsCC { + // (undocumented) + command: T; +} + +// Warning: (ae-missing-release-tag) "containsCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function containsCC(container: T | undefined): container is T & ContainsCC; + +// Warning: (ae-missing-release-tag) "ContainsSerializedCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ContainsSerializedCC { + // (undocumented) + serializedCC: Buffer; +} + +// Warning: (ae-missing-release-tag) "containsSerializedCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function containsSerializedCC(container: T | undefined): container is T & ContainsSerializedCC; + // Warning: (ae-forgotten-export) The symbol "ControllerEventCallbacks" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ControllerEvents" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -385,7 +438,7 @@ export class DeviceClass { // Warning: (ae-missing-release-tag) "Driver" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export class Driver extends TypedEventEmitter implements ZWaveApplicationHost { +export class Driver extends TypedEventEmitter implements CCAPIHost, InterviewContext, RefreshValuesContext, PersistValuesContext { // (undocumented) [util.inspect.custom](): string; constructor(port: string | ZWaveSerialPortImplementation, ...optionsAndPresets: (PartialZWaveOptions | undefined)[]); @@ -395,15 +448,14 @@ export class Driver extends TypedEventEmitter implements Z checkForConfigUpdates(silent?: boolean): Promise; // Warning: (ae-forgotten-export) The symbol "SendDataRequest" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SendDataBridgeRequest" needs to be exported by the entry point index.d.ts - computeNetCCPayloadSize(commandOrMsg: CommandClass | SendDataRequest | SendDataBridgeRequest, ignoreEncapsulation?: boolean): number; + computeNetCCPayloadSize(commandOrMsg: CommandClass | (SendDataRequest | SendDataBridgeRequest) & ContainsCC, ignoreEncapsulation?: boolean): number; // (undocumented) readonly configManager: ConfigManager; // (undocumented) get configVersion(): string; get controller(): ZWaveController; - get controllerLog(): ControllerLogger; // Warning: (ae-forgotten-export) The symbol "SendDataMessage" needs to be exported by the entry point index.d.ts - createSendDataMessage(command: CommandClass, options?: Omit): SendDataMessage; + createSendDataMessage(command: CommandClass, options?: Omit): SendDataMessage & ContainsCC; destroy(): Promise; disableStatistics(): void; // Warning: (ae-forgotten-export) The symbol "AppInfo" needs to be exported by the entry point index.d.ts @@ -416,23 +468,23 @@ export class Driver extends TypedEventEmitter implements Z }): Promise; // (undocumented) exceedsMaxPayloadLength(msg: SendDataMessage): boolean; + getCommunicationTimeouts(): ZWaveHostOptions["timeouts"]; getConservativeWaitTimeAfterFirmwareUpdate(advertisedWaitTime: number | undefined): number; // (undocumented) getDeviceConfig(nodeId: number): DeviceConfig | undefined; // (undocumented) getHighestSecurityClass(nodeId: number): MaybeNotKnown; + getInterviewOptions(): ZWaveHostOptions["interview"]; getLogConfig(): LogConfig; getMaxPayloadLength(msg: SendDataMessage): number; readonly getNextCallbackId: () => number; getNextSupervisionSessionId(nodeId: number): number; readonly getNextTransportServiceSessionId: () => number; - // (undocumented) - getNodeUnsafe(msg: Message): ZWaveNode | undefined; getReportTimeout(msg: Message): number; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - getSafeCCVersion(cc: CommandClasses_2, nodeId: number, endpointIndex?: number): number; + getSafeCCVersion(cc: CommandClasses_2, nodeId: number, endpointIndex?: number): number | undefined; // Warning: (ae-forgotten-export) The symbol "SendDataMulticastRequest" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SendDataMulticastBridgeRequest" needs to be exported by the entry point index.d.ts getSendDataMulticastConstructor(): typeof SendDataMulticastRequest | typeof SendDataMulticastBridgeRequest; @@ -442,6 +494,7 @@ export class Driver extends TypedEventEmitter implements Z // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen getSupportedCCVersion(cc: CommandClasses_2, nodeId: number, endpointIndex?: number): number; getUserAgentStringWithComponents(components?: Record): string; + getUserPreferences(): ZWaveHostOptions["preferences"]; getValueDB(nodeId: number): ValueDB; hardReset(): Promise; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -454,19 +507,21 @@ export class Driver extends TypedEventEmitter implements Z // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen isCCSecure(ccId: CommandClasses_2, nodeId: number, endpointIndex?: number): boolean; - isControllerNode(nodeId: number): boolean; // (undocumented) isInBootloader(): boolean; // (undocumented) - get nodeIdType(): NodeIDType; - get nodes(): ReadonlyThrowingMap; + logNode(nodeId: number, message: string, level?: LogNodeOptions["level"]): void; + // (undocumented) + logNode(nodeId: number, options: LogNodeOptions): void; + // (undocumented) + lookupManufacturer(manufacturerId: number): string | undefined; // (undocumented) get options(): Readonly; get ownNodeId(): number; get queueIdle(): boolean; get ready(): boolean; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - registerCommandHandler(predicate: (cc: ICommandClass) => boolean, handler: (cc: T) => void): { + registerCommandHandler(predicate: (cc: CCId) => boolean, handler: (cc: T) => void): { unregister: () => void; }; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -488,7 +543,7 @@ export class Driver extends TypedEventEmitter implements Z // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "SupervisionResult" - sendCommand(command: CommandClass, options?: SendCommandOptions): Promise>; + sendCommand(command: CommandClass, options?: SendCommandOptions): Promise>; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen sendMessage(msg: Message, options?: SendMessageOptions): Promise; @@ -501,13 +556,15 @@ export class Driver extends TypedEventEmitter implements Z get statisticsEnabled(): boolean; // (undocumented) tryGetEndpoint(cc: CommandClass): Endpoint | undefined; + // (undocumented) + tryGetNode(msg: Message): ZWaveNode | undefined; tryGetValueDB(nodeId: number): ValueDB | undefined; trySoftReset(): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen unregisterRequestHandler(fnType: FunctionType, handler: RequestHandler): void; // (undocumented) - unwrapCommands(msg: Message & ICommandClassContainer): void; + unwrapCommands(msg: Message & ContainsCC): void; updateLogConfig(config: Partial): void; updateOptions(options: EditableZWaveOptions): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -518,7 +575,7 @@ export class Driver extends TypedEventEmitter implements Z waitForBootloaderChunk(predicate: (chunk: BootloaderChunk) => boolean, timeout: number): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - waitForCommand(predicate: (cc: ICommandClass) => boolean, timeout: number): Promise; + waitForCommand(predicate: (cc: CCId) => boolean, timeout: number): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -591,7 +648,7 @@ export type EditableZWaveOptions = Expand; getCCVersion(cc: CommandClasses_2): number; - getNodeUnsafe(): ZWaveNode | undefined; getSupportedCCInstances(): readonly CommandClass[]; readonly index: number; get installerIcon(): MaybeNotKnown; @@ -623,6 +679,7 @@ export class Endpoint implements IZWaveEndpoint { protected reset(): void; supportsCC(cc: CommandClasses_2): boolean; supportsCCAPI(cc: CCNameOrId): boolean; + tryGetNode(): ZWaveNode | undefined; get userIcon(): MaybeNotKnown; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "IZWaveEndpoint" readonly virtual = false; @@ -840,6 +897,16 @@ export interface InclusionUserCallbacks { export { InterviewStage } +// Warning: (ae-missing-release-tag) "isCommandRequest" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function isCommandRequest(msg: Message): msg is CommandRequest; + +// Warning: (ae-missing-release-tag) "isMessageWithCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function isMessageWithCC(msg: Message): msg is SendDataMessage | CommandRequest; + export { isRssiError } // Warning: (ae-missing-release-tag) "JoinNetworkOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1079,6 +1146,16 @@ export { MessagePriority } export { MessageType } +// Warning: (ae-missing-release-tag) "MessageWithCC" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface MessageWithCC { + // (undocumented) + command: CommandClass | undefined; + // (undocumented) + serializedCC: Buffer | undefined; +} + // Warning: (ae-missing-release-tag) "MPDU" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1272,6 +1349,7 @@ export { QRProvisioningInformation } // // @public (undocumented) export interface RebuildRoutesOptions { + deletePriorityReturnRoutes?: boolean; includeSleeping?: boolean; } @@ -1457,7 +1535,7 @@ export { ValueType } // Warning: (ae-missing-release-tag) "VirtualEndpoint" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export class VirtualEndpoint implements IVirtualEndpoint { +export class VirtualEndpoint implements VirtualEndpointId, SupportsCC_2 { constructor( node: VirtualNode | undefined, driver: Driver, @@ -1470,7 +1548,9 @@ export class VirtualEndpoint implements IVirtualEndpoint { // (undocumented) get node(): VirtualNode; // (undocumented) - get nodeId(): number | MulticastDestination; + get nodeId(): number | MulticastDestination_2; + // (undocumented) + protected setNode(node: VirtualNode): void; supportsCC(cc: CommandClasses): boolean; supportsCCAPI(cc: CommandClasses): boolean; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "IZWaveEndpoint" @@ -1480,7 +1560,7 @@ export class VirtualEndpoint implements IVirtualEndpoint { // Warning: (ae-missing-release-tag) "VirtualNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class VirtualNode extends VirtualEndpoint implements IVirtualNode { +export class VirtualNode extends VirtualEndpoint { constructor(id: number | undefined, driver: Driver, physicalNodes: Iterable); getDefinedValueIDs(): VirtualValueID[]; @@ -1521,10 +1601,12 @@ export class Zniffer extends TypedEventEmitter { clearCapturedFrames(): void; get currentFrequency(): number | undefined; destroy(): Promise; - getCaptureAsZLFBuffer(): Buffer; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + getCaptureAsZLFBuffer(frameFilter?: (frame: CapturedFrame) => boolean): Buffer; // (undocumented) init(): Promise; - saveCaptureToFile(filePath: string): Promise; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + saveCaptureToFile(filePath: string, frameFilter?: (frame: CapturedFrame) => boolean): Promise; // (undocumented) setFrequency(frequency: number): Promise; start(): Promise; @@ -1674,6 +1756,7 @@ export class ZWaveController extends TypedEventEmitter // Warning: (ae-forgotten-export) The symbol "SerialAPISetup_GetPowerlevelResponse" needs to be exported by the entry point index.d.ts getPowerlevel(): Promise>; getPriorityReturnRouteCached(nodeId: number, destinationNodeId: number): MaybeUnknown | undefined; + getPriorityReturnRoutesCached(nodeId: number): Record; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen getPriorityRoute(destinationNodeId: number): Promise<{ routeKind: RouteKind.LWR | RouteKind.NLWR | RouteKind.Application; @@ -1965,28 +2048,25 @@ export class ZWaveMPDU implements MPDU { toLogEntry(): MessageOrCCLogEntry; } -// Warning: (ae-forgotten-export) The symbol "StatisticsEventCallbacksWithSelf" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "AllNodeEvents" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "NodeStatisticsHost" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ZWaveNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // Warning: (ae-missing-release-tag) "ZWaveNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ZWaveNode extends TypedEventEmitter>, NodeStatisticsHost { +export interface ZWaveNode extends TypedEventEmitter, NodeStatisticsHost { } +// Warning: (ae-forgotten-export) The symbol "ZWaveNodeMixins" needs to be exported by the entry point index.d.ts +// // @public -export class ZWaveNode extends Endpoint implements SecurityClassOwner, IZWaveNode { +export class ZWaveNode extends ZWaveNodeMixins implements QuerySecurityClasses { constructor(id: number, driver: Driver, deviceClass?: DeviceClass, supportedCCs?: CommandClasses_2[], controlledCCs?: CommandClasses_2[], valueDB?: ValueDB); - abortFirmwareUpdate(): Promise; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "checkLifelineHealth" // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "checkRouteHealth" abortHealthCheck(): void; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "checkLinkReliability" abortLinkReliabilityCheck(): void; - // (undocumented) - get aggregatedEndpointCount(): MaybeNotKnown; - // (undocumented) - get canSleep(): MaybeNotKnown; checkLifelineHealth(rounds?: number, onProgress?: (round: number, totalRounds: number, lastRating: number, lastResult: LifelineHealthCheckResult) => void): Promise; checkLinkReliability(options: LinkReliabilityCheckOptions): Promise; checkRouteHealth(targetNodeId: number, rounds?: number, onProgress?: (round: number, totalRounds: number, lastRating: number, lastResult: RouteHealthCheckResult) => void): Promise; @@ -2001,54 +2081,25 @@ export class ZWaveNode extends Endpoint implements SecurityClassOwner, IZWaveNod get deviceDatabaseUrl(): MaybeNotKnown; get dsk(): Buffer | undefined; // (undocumented) - get endpointCountIsDynamic(): MaybeNotKnown; - // (undocumented) - get endpointsHaveIdenticalCapabilities(): MaybeNotKnown; + protected _emit(event: TEvent, ...args: Parameters): boolean; // (undocumented) get firmwareVersion(): MaybeNotKnown; - getAllEndpoints(): Endpoint[]; getDateAndTime(): Promise; getDefinedValueIDs(): TranslatedValueID_2[]; - getEndpoint(index: 0): Endpoint; - // (undocumented) - getEndpoint(index: number): Endpoint | undefined; - getEndpointCount(): number; - getEndpointIndizes(): number[]; - // (undocumented) - getEndpointOrThrow(index: number): Endpoint; getFirmwareUpdateCapabilities(): Promise; getFirmwareUpdateCapabilitiesCached(): FirmwareUpdateCapabilities; - getHighestSecurityClass(): MaybeNotKnown; - getValue(valueId: ValueID_2): MaybeNotKnown; - getValueMetadata(valueId: ValueID_2): ValueMetadata_2; - getValueTimestamp(valueId: ValueID_2): MaybeNotKnown; // (undocumented) get hardwareVersion(): MaybeNotKnown; hasDeviceConfigChanged(): MaybeNotKnown; - // (undocumented) - hasSecurityClass(securityClass: SecurityClass_2): MaybeNotKnown; get hasSUCReturnRoute(): boolean; set hasSUCReturnRoute(value: boolean); - // (undocumented) - readonly id: number; - // (undocumented) - get individualEndpointCount(): MaybeNotKnown; interview(): Promise; get interviewAttempts(): number; interviewCC(cc: CommandClasses_2): Promise; protected interviewCCs(): Promise; protected interviewNodeInfo(): Promise; - get interviewStage(): InterviewStage; - set interviewStage(value: InterviewStage); - get isControllerNode(): boolean; - isFirmwareUpdateInProgress(): boolean; - get isFrequentListening(): MaybeNotKnown; isHealthCheckInProgress(): boolean; isLinkReliabilityCheckInProgress(): boolean; - get isListening(): MaybeNotKnown; - get isRouting(): MaybeNotKnown; - get isSecure(): MaybeNotKnown; - keepAwake: boolean; // (undocumented) get label(): string | undefined; get lastSeen(): MaybeNotKnown; @@ -2060,11 +2111,12 @@ export class ZWaveNode extends Endpoint implements SecurityClassOwner, IZWaveNod manuallyIdleNotificationValue(notificationType: number, prevValue: number, endpointIndex?: number): void; // (undocumented) get manufacturerId(): MaybeNotKnown; - // (undocumented) - get maxDataRate(): MaybeNotKnown; get name(): MaybeNotKnown; set name(value: string | undefined); - get nodeType(): MaybeNotKnown; + // (undocumented) + protected _on(event: TEvent, callback: AllNodeEvents[TEvent]): this; + // (undocumented) + protected _once(event: TEvent, callback: AllNodeEvents[TEvent]): this; protected overwriteConfig(): Promise; ping(): Promise; pollValue(valueId: ValueID_2, sendCommandOptions?: SendCommandOptions): Promise>; @@ -2072,10 +2124,7 @@ export class ZWaveNode extends Endpoint implements SecurityClassOwner, IZWaveNod get productId(): MaybeNotKnown; // (undocumented) get productType(): MaybeNotKnown; - get protocol(): Protocols; - get protocolVersion(): MaybeNotKnown; protected queryProtocolInfo(): Promise; - get ready(): boolean; refreshCCValues(cc: CommandClasses_2): Promise; refreshInfo(options?: RefreshInfoOptions): Promise; refreshValues(): Promise; @@ -2086,20 +2135,10 @@ export class ZWaveNode extends Endpoint implements SecurityClassOwner, IZWaveNod // (undocumented) sendResetLocallyNotification(): Promise; setDateAndTime(now?: Date): Promise; - // (undocumented) - setSecurityClass(securityClass: SecurityClass_2, granted: boolean): void; setValue(valueId: ValueID_2, value: unknown, options?: SetValueAPIOptions): Promise; - get status(): NodeStatus; - // (undocumented) - get supportedDataRates(): MaybeNotKnown; - get supportsBeaming(): MaybeNotKnown; - get supportsSecurity(): MaybeNotKnown; // (undocumented) get supportsWakeUpOnDemand(): MaybeNotKnown; testPowerlevel(testNodeId: number, powerlevel: Powerlevel_2, healthCheckTestFrameCount: number, onProgress?: (acknowledged: number, total: number) => void): Promise; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - updateFirmware(updates: Firmware[], options?: FirmwareUpdateOptions): Promise; - waitForWakeup(): Promise; // (undocumented) get zwavePlusNodeType(): MaybeNotKnown; // (undocumented) @@ -2125,6 +2164,8 @@ export interface ZWaveNodeEventCallbacks extends ZWaveNodeValueEventCallbacks { // (undocumented) "interview started": (node: ZWaveNode) => void; // (undocumented) + "node info received": (node: ZWaveNode) => void; + // (undocumented) "wake up": ZWaveNodeStatusChangeCallback; // (undocumented) alive: ZWaveNodeStatusChangeCallback; @@ -2418,21 +2459,20 @@ export * from "@zwave-js/cc"; // Warnings were encountered during analysis: // -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ColorSwitchCC.ts:478:9 - (TS2345) Argument of type '("index" | "warmWhite" | "coldWhite" | "red" | "green" | "blue" | "amber" | "cyan" | "purple" | undefined)[]' is not assignable to parameter of type 'readonly (string | number | symbol)[]'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ColorSwitchCC.ts:480:9 - (TS2345) Argument of type '("index" | "warmWhite" | "coldWhite" | "red" | "green" | "blue" | "amber" | "cyan" | "purple" | undefined)[]' is not assignable to parameter of type 'readonly (string | number | symbol)[]'. // Type 'string | undefined' is not assignable to type 'string | number | symbol'. // Type 'undefined' is not assignable to type 'string | number | symbol'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1273:41 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1283:36 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1280:20 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1290:20 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1398:40 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/ConfigurationCC.ts:1414:35 - (TS2345) Argument of type 'string | number' is not assignable to parameter of type 'number'. // Type 'string' is not assignable to type 'number'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1456:3 - (TS2322) Type 'Security2Extension | undefined' is not assignable to type 'MGRPExtension | undefined'. -// Property 'groupId' is missing in type 'Security2Extension' but required in type 'MGRPExtension'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1467:3 - (TS2322) Type 'Security2Extension | undefined' is not assignable to type 'MPANExtension | undefined'. -// Type 'Security2Extension' is missing the following properties from type 'MPANExtension': groupId, innerMPANState -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1481:25 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. -// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1542:19 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:450:24 - (TS2339) Property 'groupId' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:458:24 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1655:20 - (TS2339) Property 'groupId' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1658:34 - (TS2339) Property 'innerMPANState' does not exist on type 'Security2Extension'. +// /home/dominic/Repositories/node-zwave-js/packages/cc/src/cc/Security2CC.ts:1808:19 - (TS2339) Property 'senderEI' does not exist on type 'Security2Extension'. // /home/dominic/Repositories/node-zwave-js/packages/nvmedit/src/lib/NVM3.ts:430:20 - (TS18048) 'h' is possibly 'undefined'. // /home/dominic/Repositories/node-zwave-js/packages/nvmedit/src/lib/NVM3.ts:433:33 - (TS18048) 'header' is possibly 'undefined'. // /home/dominic/Repositories/node-zwave-js/packages/nvmedit/src/lib/NVM3.ts:434:54 - (TS18048) 'header' is possibly 'undefined'. @@ -2440,20 +2480,20 @@ export * from "@zwave-js/cc"; // /home/dominic/Repositories/node-zwave-js/packages/nvmedit/src/lib/NVM3.ts:439:11 - (TS18048) 'header' is possibly 'undefined'. // /home/dominic/Repositories/node-zwave-js/packages/nvmedit/src/lib/NVM3.ts:440:12 - (TS18048) 'header' is possibly 'undefined'. // src/lib/controller/Controller.ts:997:2 - (ae-missing-getter) The property "provisioningList" has a setter but no getter. -// src/lib/controller/Controller.ts:7564:3 - (ae-forgotten-export) The symbol "ExtendedNVMOperationsCommand" needs to be exported by the entry point index.d.ts -// src/lib/driver/Driver.ts:735:24 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag -// src/lib/driver/Driver.ts:4472:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/driver/Driver.ts:5621:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "drainSerialAPIQueue" -// src/lib/driver/Driver.ts:6014:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/driver/Driver.ts:6015:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/driver/Driver.ts:6057:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/driver/Driver.ts:6058:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/driver/Driver.ts:6194:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/controller/Controller.ts:7605:3 - (ae-forgotten-export) The symbol "ExtendedNVMOperationsCommand" needs to be exported by the entry point index.d.ts +// src/lib/driver/Driver.ts:838:24 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag +// src/lib/driver/Driver.ts:4678:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/driver/Driver.ts:5838:2 - (ae-unresolved-link) The @link reference could not be resolved: The package "zwave-js" does not have an export "drainSerialAPIQueue" +// src/lib/driver/Driver.ts:6240:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/driver/Driver.ts:6241:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/driver/Driver.ts:6283:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/driver/Driver.ts:6284:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/driver/Driver.ts:6430:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/lib/driver/ZWaveOptions.ts:280:120 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag -// src/lib/node/Node.ts:1033:2 - (ae-missing-getter) The property "deviceConfigHash" has a setter but no getter. -// src/lib/node/Node.ts:3019:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/zniffer/Zniffer.ts:631:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/lib/zniffer/Zniffer.ts:632:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/node/Node.ts:556:2 - (ae-missing-getter) The property "deviceConfigHash" has a setter but no getter. +// src/lib/node/Node.ts:2243:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/zniffer/Zniffer.ts:700:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/lib/zniffer/Zniffer.ts:701:5 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // (No @packageDocumentation comment for this package) diff --git a/server_config.js b/server_config.js index 6f5b3fec5db8..eabf48f1cd14 100644 --- a/server_config.js +++ b/server_config.js @@ -49,28 +49,21 @@ module.exports.default = { }, behaviors: [ { - async onControllerFrame(controller, self, frame) { + async handleCC(controller, self, receivedCC) { if ( - frame.type === MockZWaveFrameType.Request - && frame.payload instanceof SupervisionCCGet - && frame.payload.encapsulated + receivedCC instanceof SupervisionCCGet + && receivedCC.encapsulated instanceof ConfigurationCCSet ) { - const cc = new SupervisionCCReport(self.host, { - nodeId: controller.host.ownNodeId, - sessionId: frame.payload.sessionId, + const cc = new SupervisionCCReport({ + nodeId: controller.ownNodeId, + sessionId: receivedCC.sessionId, moreUpdatesFollow: false, status: SupervisionStatus.Fail, }); - // await wait(500); - await self.sendToController( - createMockZWaveRequestFrame(cc, { - ackRequested: false, - }), - ); - return true; + + return { action: "sendCC", cc }; } - return false; }, }, ], diff --git a/test/decodeMessage.ts b/test/decodeMessage.ts index 90942ac69462..8eba4c6a7ca7 100644 --- a/test/decodeMessage.ts +++ b/test/decodeMessage.ts @@ -2,16 +2,18 @@ import "reflect-metadata"; import "zwave-js"; -import { isCommandClassContainer } from "@zwave-js/cc"; import { ConfigManager } from "@zwave-js/config"; import { + NodeIDType, SPANState, SecurityClass, + type SecurityManager, SecurityManager2, generateAuthKey, generateEncryptionKey, } from "@zwave-js/core"; import { Message } from "@zwave-js/serial"; +import { containsCC } from "zwave-js"; (async () => { const configManager = new ConfigManager(); @@ -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 @@ -52,8 +54,7 @@ import { Message } from "@zwave-js/serial"; receiverEI: Buffer.from("3664023a7971465342fe3d82ebb4b8e9", "hex"), }); - console.log(Message.getMessageLength(data)); - const host: any = { + const host = { getSafeCCVersion: () => 1, getSupportedCCVersion: () => 1, configManager, @@ -81,21 +82,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.parse(data, ctx as any); - if (isCommandClassContainer(msg)) { - msg.command.mergePartialCCs(host, []); + if (containsCC(msg)) { + msg.command.mergePartialCCs([], {} as any); } msg; debugger; diff --git a/yarn.lock b/yarn.lock index 186ad3491e5a..8ffbe80a3e1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2010,7 +2010,6 @@ __metadata: "@zwave-js/core": "workspace:*" "@zwave-js/host": "workspace:*" "@zwave-js/maintenance": "workspace:*" - "@zwave-js/serial": "workspace:*" "@zwave-js/shared": "workspace:*" "@zwave-js/transformers": "workspace:*" alcalzone-shared: "npm:^4.0.8" @@ -2069,6 +2068,7 @@ __metadata: "@alcalzone/jsonl-db": "npm:^3.1.1" "@microsoft/api-extractor": "npm:^7.47.9" "@types/node": "npm:^18.19.55" + "@types/semver": "npm:^7.5.8" "@types/sinon": "npm:^17.0.3" "@types/triple-beam": "npm:^1.3.5" "@zwave-js/shared": "workspace:*" @@ -2082,6 +2082,7 @@ __metadata: logform: "npm:^2.6.1" nrf-intel-hex: "npm:^1.4.0" reflect-metadata: "npm:^0.2.2" + semver: "npm:^7.6.3" sinon: "npm:^19.0.2" triple-beam: "npm:*" typescript: "npm:5.6.2" @@ -2289,6 +2290,7 @@ __metadata: "@serialport/stream": "npm:^12.0.0" "@types/node": "npm:^18.19.55" "@types/sinon": "npm:^17.0.3" + "@zwave-js/cc": "workspace:*" "@zwave-js/core": "workspace:*" "@zwave-js/host": "workspace:*" "@zwave-js/shared": "workspace:*"